MythTV master
mythnews.cpp
Go to the documentation of this file.
1// POSIX headers
2#include <unistd.h>
3
4// C headers
5#include <algorithm>
6#include <cmath>
7
8// QT headers
9#include <QCoreApplication>
10#include <QDateTime>
11#include <QDir>
12#include <QRegularExpression>
13#include <QTimer>
14#include <QUrl>
15
16// MythTV headers
19#include <libmythbase/mythdb.h>
29
30// MythNews headers
31#include "mythnews.h"
32#include "mythnewsconfig.h"
33#include "mythnewseditor.h"
34#include "newsdbutil.h"
35
36#define LOC QString("MythNews: ")
37#define LOC_WARN QString("MythNews, Warning: ")
38#define LOC_ERR QString("MythNews, Error: ")
39
44MythNews::MythNews(MythScreenStack *parent, const QString &name) :
45 MythScreenType(parent, name),
46 m_retrieveTimer(new QTimer(this)),
47 m_updateFreq(gCoreContext->GetDurSetting<std::chrono::minutes>("NewsUpdateFrequency", 30min)),
48 m_zoom(gCoreContext->GetSetting("WebBrowserZoomLevel", "1.0")),
49 m_browser(gCoreContext->GetSetting("WebBrowserCommand", ""))
50{
51 // Setup cache directory
52
53 QString fileprefix = GetConfDir();
54
55 QDir dir(fileprefix);
56 if (!dir.exists())
57 dir.mkdir(fileprefix);
58 fileprefix += "/MythNews";
59 dir.setPath(fileprefix);;
60 if (!dir.exists())
61 dir.mkdir(fileprefix);
62
65
66 m_retrieveTimer->stop();
67 m_retrieveTimer->setSingleShot(false);
69}
70
72{
73 QMutexLocker locker(&m_lock);
74}
75
77{
78 QMutexLocker locker(&m_lock);
79
80 // Load the theme for this screen
81 bool foundtheme = LoadWindowFromXML("news-ui.xml", "news", this);
82 if (!foundtheme)
83 return false;
84
85 bool err = false;
86 UIUtilE::Assign(this, m_sitesList, "siteslist", &err);
87 UIUtilE::Assign(this, m_articlesList, "articleslist", &err);
88 UIUtilE::Assign(this, m_titleText, "title", &err);
89 UIUtilE::Assign(this, m_descText, "description", &err);
90
91 // these are all optional
92 UIUtilW::Assign(this, m_nositesText, "nosites", &err);
93 UIUtilW::Assign(this, m_updatedText, "updated", &err);
94 UIUtilW::Assign(this, m_thumbnailImage, "thumbnail", &err);
95 UIUtilW::Assign(this, m_enclosureImage, "enclosures", &err);
96 UIUtilW::Assign(this, m_downloadImage, "download", &err);
97 UIUtilW::Assign(this, m_podcastImage, "ispodcast", &err);
98
99 if (err)
100 {
101 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'news'");
102 return false;
103 }
104
105 if (m_nositesText)
106 {
107 m_nositesText->SetText(tr("You haven't configured MythNews to use any sites."));
109 }
110
112
114
115 loadSites();
117
121 this, qOverload<MythUIButtonListItem *>(&MythNews::updateInfoView));
124
125 return true;
126}
127
129{
132 m_articles.clear();
134
136 m_descText->Reset();
137
138 if (m_updatedText)
140
141 if (m_downloadImage)
143
146
147 if (m_podcastImage)
149
152}
153
155{
156 QMutexLocker locker(&m_lock);
157
158 clearSites();
159
161 query.prepare(
162 "SELECT name, url, ico, updated, podcast "
163 "FROM newssites "
164 "ORDER BY name");
165
166 if (!query.exec())
167 {
168 MythDB::DBError(LOC_ERR + "Could not load sites from DB", query);
169 return;
170 }
171
172 while (query.next())
173 {
174 QString name = query.value(0).toString();
175 QString url = query.value(1).toString();
176// QString icon = query.value(2).toString();
177 QDateTime time = MythDate::fromSecsSinceEpoch(query.value(3).toLongLong());
178 bool podcast = query.value(4).toBool();
179 m_newsSites.push_back(new NewsSite(name, url, time, podcast));
180 }
181 std::ranges::sort(m_newsSites, NewsSite::sortByName);
182
183 for (auto & site : m_newsSites)
184 {
185 auto *item = new MythUIButtonListItem(m_sitesList, site->name());
186 item->SetData(QVariant::fromValue(site));
187
188 connect(site, &NewsSite::finished,
190 }
191
193
194 if (m_nositesText)
195 {
196 if (m_newsSites.empty())
198 else
200 }
201}
202
204{
205 QMutexLocker locker(&m_lock);
206
207 if (!selected)
208 return;
209
210 NewsSite *site = nullptr;
211 NewsArticle article;
212
214 {
215 article = m_articles[selected];
217 site = m_sitesList->GetItemCurrent()->GetData().value<NewsSite*>();
218 }
219 else
220 {
221 site = selected->GetData().value<NewsSite*>();
224 }
225
227 {
228 if (!article.title().isEmpty())
229 {
230
231 if (m_titleText)
232 {
233 QString title = cleanText(article.title());
234 m_titleText->SetText(title);
235 }
236
237 if (m_descText)
238 {
239 QString artText = cleanText(article.description());
240 m_descText->SetText(artText);
241 }
242
243 if (!article.thumbnail().isEmpty())
244 {
246 {
249
252 }
253 }
254 else
255 {
256 if (site && !site->imageURL().isEmpty())
257 {
259 {
262
265 }
266 }
267 else
268 {
271 }
272 }
273
274 if (m_downloadImage)
275 {
276 if (!article.enclosure().isEmpty())
277 {
280 }
281 else
282 {
284 }
285 }
286
288 {
289 if (!article.enclosure().isEmpty())
290 {
293 }
294 else
295 {
297 }
298 }
299
300 if (m_podcastImage)
302 }
303 }
304 else
305 {
306 if (m_downloadImage)
308
311
312 if (m_podcastImage)
314
315 if (site)
316 {
317 if (m_titleText)
318 m_titleText->SetText(site->name());
319
320 if (m_descText)
322
325
326 if (m_podcastImage && site->podcast())
328
329 if (!site->imageURL().isEmpty())
330 {
332 {
335
338 }
339 }
340 }
341 }
342
343 if (m_updatedText)
344 {
345
346 if (site)
347 {
348 QString text(tr("Updated") + " - ");
349 QDateTime updated(site->lastUpdated());
350 if (updated.isValid()) {
351 text += MythDate::toString(site->lastUpdated(),
353 }
354 else
355 {
356 text += tr("Unknown");
357 }
358 m_updatedText->SetText(text);
359 }
360 }
361}
362
363bool MythNews::keyPressEvent(QKeyEvent *event)
364{
366 return true;
367
368 QStringList actions;
369 bool handled = GetMythMainWindow()->TranslateKeyPress("News", event, actions);
370
371 for (int i = 0; i < actions.size() && !handled; i++)
372 {
373 const QString& action = actions[i];
374 handled = true;
375
376 if (action == "RETRIEVENEWS")
378 else if (action == "CANCEL")
380 else if (action == "MENU")
381 ShowMenu();
382 else if (action == "EDIT")
383 ShowEditDialog(true);
384 else if (action == "DELETE")
386 else
387 handled = false;
388 }
389
390 if (!handled && MythScreenType::keyPressEvent(event))
391 handled = true;
392
393 return handled;
394}
395
397{
398 QMutexLocker locker(&m_lock);
399
400 if (m_newsSites.empty())
401 return;
402
403 m_retrieveTimer->stop();
404
405 for (auto & site : m_newsSites)
406 {
407 if (site->timeSinceLastUpdate() > m_updateFreq)
408 site->retrieve();
409 else
410 processAndShowNews(site);
411 }
412
413 m_retrieveTimer->stop();
414 m_retrieveTimer->setSingleShot(false);
416}
417
419{
420 qint64 updated = site->lastUpdated().toSecsSinceEpoch();
421
423 query.prepare("UPDATE newssites SET updated = :UPDATED "
424 "WHERE name = :NAME ;");
425 query.bindValue(":UPDATED", updated);
426 query.bindValue(":NAME", site->name());
427 if (!query.exec() || !query.isActive())
428 MythDB::DBError("news update time", query);
429
430 processAndShowNews(site);
431}
432
434{
435 QMutexLocker locker(&m_lock);
436
437 for (auto & site : m_newsSites)
438 {
439 site->stop();
440 processAndShowNews(site);
441 }
442}
443
445{
446 QMutexLocker locker(&m_lock);
447
448 if (!site)
449 return;
450
451 site->process();
452
454 if (!siteUIItem)
455 return;
456
457 if (site != siteUIItem->GetData().value<NewsSite*>())
458 return;
459
460 QString currItem = m_articlesList->GetValue();
461 int topPos = m_articlesList->GetTopItemPos();
462
464 m_articles.clear();
465
466 NewsArticle::List articles = site->GetArticleList();
467 for (auto & article : articles)
468 {
469 auto *item =
470 new MythUIButtonListItem(m_articlesList, cleanText(article.title()));
471 m_articles[item] = article;
472 }
473
474 if (m_articlesList->MoveToNamedPosition(currItem))
476}
477
479{
480 QMutexLocker locker(&m_lock);
481
482 if (!item || item->GetData().isNull())
483 return;
484
485 auto *site = item->GetData().value<NewsSite*>();
486 if (!site)
487 return;
488
490 m_articles.clear();
491
492 NewsArticle::List articles = site->GetArticleList();
493 for (auto & article : articles)
494 {
495 auto *blitem = new MythUIButtonListItem(m_articlesList, cleanText(article.title()));
496 m_articles[blitem] = article;
497 }
498
499 updateInfoView(item);
500}
501
503{
504 QMutexLocker locker(&m_lock);
505
506 QMap<MythUIButtonListItem*,NewsArticle>::const_iterator it =
507 m_articles.constFind(articlesListItem);
508
509 if (it == m_articles.constEnd())
510 return;
511
512 const NewsArticle& article = *it;
513
514 if (article.articleURL().isEmpty())
515 return;
516
517 if (article.enclosure().isEmpty())
518 {
519 QString cmdUrl(article.articleURL());
520
521 if (m_browser.isEmpty())
522 {
523 ShowOkPopup(tr("No browser command set! MythNews needs MythBrowser to be installed."));
524 return;
525 }
526
527 // display the web page
528 if (m_browser.toLower() == "internal")
529 {
530 GetMythMainWindow()->HandleMedia("WebBrowser", cmdUrl);
531 return;
532 }
533
534 QString cmd = m_browser;
535 cmd.replace("%ZOOM%", m_zoom);
536 cmd.replace("%URL%", cmdUrl);
537 cmd.replace('\'', "%27");
538 cmd.replace("&","\\&");
539 cmd.replace(";","\\;");
540
544 return;
545 }
546
547 playVideo(article);
548}
549
551{
552 QMutexLocker locker(&m_lock);
553
554 NewsSite *site = nullptr;
555
556 if (edit)
557 {
559
560 if (!siteListItem || siteListItem->GetData().isNull())
561 return;
562
563 site = siteListItem->GetData().value<NewsSite*>();
564 }
565
567
568 auto *mythnewseditor = new MythNewsEditor(site, edit, mainStack,
569 "mythnewseditor");
570
571 if (mythnewseditor->Create())
572 {
573 connect(mythnewseditor, &MythScreenType::Exiting, this, &MythNews::loadSites);
574 mainStack->AddScreen(mythnewseditor);
575 }
576 else
577 {
578 delete mythnewseditor;
579 }
580}
581
583{
585
586 auto *mythnewsconfig = new MythNewsConfig(mainStack, "mythnewsconfig");
587
588 if (mythnewsconfig->Create())
589 {
590 connect(mythnewsconfig, &MythScreenType::Exiting, this, &MythNews::loadSites);
591 mainStack->AddScreen(mythnewsconfig);
592 }
593 else
594 {
595 delete mythnewsconfig;
596 }
597}
598
600{
601 QMutexLocker locker(&m_lock);
602
603 QString label = tr("Options");
604
605 MythScreenStack *popupStack =
606 GetMythMainWindow()->GetStack("popup stack");
607
608 m_menuPopup = new MythDialogBox(label, popupStack, "mythnewsmenupopup");
609
610 if (m_menuPopup->Create())
611 {
612 popupStack->AddScreen(m_menuPopup);
613
614 m_menuPopup->SetReturnEvent(this, "options");
615
616 m_menuPopup->AddButton(tr("Manage Feeds"));
617 m_menuPopup->AddButton(tr("Add Feed"));
618 if (!m_newsSites.empty())
619 {
620 m_menuPopup->AddButton(tr("Edit Feed"));
621 m_menuPopup->AddButton(tr("Delete Feed"));
622 }
623 }
624 else
625 {
626 delete m_menuPopup;
627 m_menuPopup = nullptr;
628 }
629}
630
632{
633 QMutexLocker locker(&m_lock);
634
636
637 if (siteUIItem && !siteUIItem->GetData().isNull())
638 {
639 auto *site = siteUIItem->GetData().value<NewsSite*>();
640 if (site)
641 {
642 removeFromDB(site->name());
643 loadSites();
644 }
645 }
646}
647
648// does not need locking
650{
651 GetMythMainWindow()->HandleMedia("Internal", article.enclosure(),
652 article.description(), article.title());
653}
654
655// does not need locking
656void MythNews::customEvent(QEvent *event)
657{
658 if (event->type() == DialogCompletionEvent::kEventType)
659 {
660 auto *dce = dynamic_cast<DialogCompletionEvent*>(event);
661 if (dce == nullptr)
662 return;
663
664 QString resultid = dce->GetId();
665 int buttonnum = dce->GetResult();
666
667 if (resultid == "options")
668 {
669 if (buttonnum == 0)
671 else if (buttonnum == 1)
672 ShowEditDialog(false);
673 else if (buttonnum == 2)
674 ShowEditDialog(true);
675 else if (buttonnum == 3)
677 }
678
679 m_menuPopup = nullptr;
680 }
681}
682
683QString MythNews::cleanText(const QString &text)
684{
685 QString result = text;
686
687 // replace a few HTML characters
688 result.replace("&#8232;", ""); // LSEP
689 result.replace("&#8233;", ""); // PSEP
690 result.replace("&#163;", "£"); // POUND
691 result.replace("&#173;", ""); // ?
692 result.replace("&#8211;", "-"); // EN-DASH
693 result.replace("&#8220;", """"); // LEFT-DOUBLE-QUOTE
694 result.replace("&#8221;", """"); // RIGHT-DOUBLE-QUOTE
695 result.replace("&#8216;", "'"); // LEFT-SINGLE-QUOTE
696 result.replace("&#8217;", "'"); // RIGHT-SINGLE-QUOTE
697 result.replace("&#39;", "'"); // Apostrophe
698
699 // Replace paragraph and break HTML with newlines
700 static const QRegularExpression kHtmlParaStartRE
701 { "<p>", QRegularExpression::CaseInsensitiveOption };
702 static const QRegularExpression kHtmlParaEndRE
703 { "</p>", QRegularExpression::CaseInsensitiveOption };
704 static const QRegularExpression kHtmlBreak1RE
705 { "<(br|)>", QRegularExpression::CaseInsensitiveOption };
706 static const QRegularExpression kHtmlBreak2RE
707 { "<(br|)/>", QRegularExpression::CaseInsensitiveOption };
708 if( result.contains(kHtmlParaEndRE) )
709 {
710 result.replace( kHtmlParaStartRE, "");
711 result.replace( kHtmlParaEndRE, "\n\n");
712 }
713 else
714 {
715 result.replace( kHtmlParaStartRE, "\n\n");
716 result.replace( kHtmlParaEndRE, "");
717 }
718 result.replace( kHtmlBreak2RE, "\n");
719 result.replace( kHtmlBreak1RE, "\n");
720 // These are done instead of simplifyWhitespace
721 // because that function also strips out newlines
722 // Replace tab characters with nothing
723 static const QRegularExpression kTabRE { "\t" };
724 result.replace( kTabRE, "");
725 // Replace double space with single
726 static const QRegularExpression kTwoSpaceRE { " " };
727 result.replace( kTwoSpaceRE, "");
728 // Replace whitespace at beginning of lines with newline
729 static const QRegularExpression kStartingSpaceRE { "\n " };
730 result.replace( kStartingSpaceRE, "\n");
731 // Remove any remaining HTML tags
732 static const QRegularExpression kRemoveHtmlRE(QRegularExpression("</?.+>"));
733 result.remove(kRemoveHtmlRE);
734 result = result.trimmed();
735
736 return result;
737}
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:40
static const Type kEventType
Definition: mythdialogbox.h:55
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
QVariant value(int i) const
Definition: mythdbcon.h:204
bool isActive(void) const
Definition: mythdbcon.h:215
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
Basic menu dialog, message and a list of options.
void AddButton(const QString &title)
void SetReturnEvent(QObject *retobject, const QString &resultid)
bool Create(void) override
MythScreenStack * GetMainStack()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
bool HandleMedia(const QString &Handler, const QString &Mrl, const QString &Plot="", const QString &Title="", const QString &Subtitle="", const QString &Director="", int Season=0, int Episode=0, const QString &Inetref="", std::chrono::minutes LenMins=2h, const QString &Year="1895", const QString &Id="", bool UseBookmarks=false)
void AllowInput(bool Allow)
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: mythnews.cpp:363
QRecursiveMutex m_lock
Definition: mythnews.h:48
void loadSites(void)
Definition: mythnews.cpp:154
void slotViewArticle(MythUIButtonListItem *articlesListItem)
Definition: mythnews.cpp:502
std::chrono::minutes m_timerTimeout
Definition: mythnews.h:52
MythUIText * m_nositesText
Definition: mythnews.h:63
void slotNewsRetrieved(NewsSite *site)
Definition: mythnews.cpp:418
static QString cleanText(const QString &text)
Definition: mythnews.cpp:683
MythUIImage * m_enclosureImage
Definition: mythnews.h:70
MythUIText * m_updatedText
Definition: mythnews.h:64
static void playVideo(const NewsArticle &article)
Definition: mythnews.cpp:649
MythUIImage * m_thumbnailImage
Definition: mythnews.h:68
void clearSites(void)
Definition: mythnews.cpp:128
void ShowFeedManager() const
Definition: mythnews.cpp:582
void cancelRetrieve(void)
Definition: mythnews.cpp:433
MythUIText * m_titleText
Definition: mythnews.h:65
MythDialogBox * m_menuPopup
Definition: mythnews.h:57
MythUIImage * m_podcastImage
Definition: mythnews.h:71
void updateInfoView(void)
NewsSite::List m_newsSites
Definition: mythnews.h:49
MythUIButtonList * m_sitesList
Definition: mythnews.h:59
QString m_zoom
Definition: mythnews.h:55
MythUIText * m_descText
Definition: mythnews.h:66
void processAndShowNews(NewsSite *site)
Definition: mythnews.cpp:444
void ShowMenu(void) override
Definition: mythnews.cpp:599
std::chrono::minutes m_updateFreq
Definition: mythnews.h:53
void deleteNewsSite(void)
Definition: mythnews.cpp:631
void ShowEditDialog(bool edit)
Definition: mythnews.cpp:550
bool Create(void) override
Definition: mythnews.cpp:76
QMap< MythUIButtonListItem *, NewsArticle > m_articles
Definition: mythnews.h:61
MythUIButtonList * m_articlesList
Definition: mythnews.h:60
MythNews(MythScreenStack *parent, const QString &name)
Creates a new MythNews Screen.
Definition: mythnews.cpp:44
void slotSiteSelected(MythUIButtonListItem *item)
Definition: mythnews.cpp:478
QTimer * m_retrieveTimer
Definition: mythnews.h:51
void customEvent(QEvent *event) override
Definition: mythnews.cpp:656
QString m_browser
Definition: mythnews.h:56
~MythNews() override
Definition: mythnews.cpp:71
MythUIImage * m_downloadImage
Definition: mythnews.h:69
void slotRetrieveNews(void)
Definition: mythnews.cpp:396
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
bool SetFocusWidget(MythUIType *widget=nullptr)
virtual QString GetValue() const
MythUIButtonListItem * GetItemCurrent() const
void SetItemCurrent(MythUIButtonListItem *item)
MythUIButtonListItem * GetItemFirst() const
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
int GetTopItemPos(void) const
int GetCurrentPos() const
void itemClicked(MythUIButtonListItem *item)
bool MoveToNamedPosition(const QString &position_name)
void itemSelected(MythUIButtonListItem *item)
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
Definition: mythuitext.cpp:65
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:115
bool IsVisible(bool recurse=false) const
Definition: mythuitype.cpp:889
void Hide(void)
void Show(void)
QString thumbnail(void) const
Definition: newsarticle.h:25
QString title(void) const
Definition: newsarticle.h:22
QString articleURL(void) const
Definition: newsarticle.h:24
QString description(void) const
Definition: newsarticle.h:23
std::vector< NewsArticle > List
Definition: newsarticle.h:13
QString enclosure(void) const
Definition: newsarticle.h:27
void clear(void)
Definition: newssite.h:66
QString imageURL(void) const
Definition: newssite.cpp:128
static bool sortByName(NewsSite *a, NewsSite *b)
Definition: newssite.h:102
bool podcast(void) const
Definition: newssite.cpp:108
NewsArticle::List GetArticleList(void) const
Definition: newssite.cpp:134
void finished(NewsSite *item)
QString name(void) const
Definition: newssite.cpp:96
QDateTime lastUpdated(void) const
Definition: newssite.cpp:140
QString description(void) const
Definition: newssite.cpp:114
void process(void)
Definition: newssite.cpp:221
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
QString GetConfDir(void)
Definition: mythdirs.cpp:285
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
#define LOC_ERR
Definition: mythnews.cpp:38
@ kMSDontDisableDrawing
avoid disabling UI drawing
Definition: mythsystem.h:37
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
MBASE_PUBLIC QDateTime fromSecsSinceEpoch(int64_t seconds)
This function takes the number of seconds since the start of the epoch and returns a QDateTime with t...
Definition: mythdate.cpp:81
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kDateTimeFull
Default local time.
Definition: mythdate.h:23
@ kSimplify
Do Today/Yesterday/Tomorrow transform.
Definition: mythdate.h:26
bool removeFromDB(RSSSite *site)
Definition: netutils.cpp:686
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27