MythTV master
mythnews.cpp
Go to the documentation of this file.
1// POSIX headers
2#include <unistd.h>
3
4// C headers
5#include <cmath>
6
7// QT headers
8#include <QCoreApplication>
9#include <QDateTime>
10#include <QDir>
11#include <QRegularExpression>
12#include <QTimer>
13#include <QUrl>
14
15// MythTV headers
18#include <libmythbase/mythdb.h>
28
29// MythNews headers
30#include "mythnews.h"
31#include "mythnewsconfig.h"
32#include "mythnewseditor.h"
33#include "newsdbutil.h"
34
35#define LOC QString("MythNews: ")
36#define LOC_WARN QString("MythNews, Warning: ")
37#define LOC_ERR QString("MythNews, Error: ")
38
43MythNews::MythNews(MythScreenStack *parent, const QString &name) :
44 MythScreenType(parent, name),
45 m_retrieveTimer(new QTimer(this)),
46 m_updateFreq(gCoreContext->GetDurSetting<std::chrono::minutes>("NewsUpdateFrequency", 30min)),
47 m_zoom(gCoreContext->GetSetting("WebBrowserZoomLevel", "1.0")),
48 m_browser(gCoreContext->GetSetting("WebBrowserCommand", ""))
49{
50 // Setup cache directory
51
52 QString fileprefix = GetConfDir();
53
54 QDir dir(fileprefix);
55 if (!dir.exists())
56 dir.mkdir(fileprefix);
57 fileprefix += "/MythNews";
58 dir.setPath(fileprefix);;
59 if (!dir.exists())
60 dir.mkdir(fileprefix);
61
64
65 m_retrieveTimer->stop();
66 m_retrieveTimer->setSingleShot(false);
68}
69
71{
72 QMutexLocker locker(&m_lock);
73}
74
76{
77 QMutexLocker locker(&m_lock);
78
79 // Load the theme for this screen
80 bool foundtheme = LoadWindowFromXML("news-ui.xml", "news", this);
81 if (!foundtheme)
82 return false;
83
84 bool err = false;
85 UIUtilE::Assign(this, m_sitesList, "siteslist", &err);
86 UIUtilE::Assign(this, m_articlesList, "articleslist", &err);
87 UIUtilE::Assign(this, m_titleText, "title", &err);
88 UIUtilE::Assign(this, m_descText, "description", &err);
89
90 // these are all optional
91 UIUtilW::Assign(this, m_nositesText, "nosites", &err);
92 UIUtilW::Assign(this, m_updatedText, "updated", &err);
93 UIUtilW::Assign(this, m_thumbnailImage, "thumbnail", &err);
94 UIUtilW::Assign(this, m_enclosureImage, "enclosures", &err);
95 UIUtilW::Assign(this, m_downloadImage, "download", &err);
96 UIUtilW::Assign(this, m_podcastImage, "ispodcast", &err);
97
98 if (err)
99 {
100 LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'news'");
101 return false;
102 }
103
104 if (m_nositesText)
105 {
106 m_nositesText->SetText(tr("You haven't configured MythNews to use any sites."));
108 }
109
111
113
114 loadSites();
116
120 this, qOverload<MythUIButtonListItem *>(&MythNews::updateInfoView));
123
124 return true;
125}
126
128{
131 m_articles.clear();
133
135 m_descText->Reset();
136
137 if (m_updatedText)
139
140 if (m_downloadImage)
142
145
146 if (m_podcastImage)
148
151}
152
154{
155 QMutexLocker locker(&m_lock);
156
157 clearSites();
158
160 query.prepare(
161 "SELECT name, url, ico, updated, podcast "
162 "FROM newssites "
163 "ORDER BY name");
164
165 if (!query.exec())
166 {
167 MythDB::DBError(LOC_ERR + "Could not load sites from DB", query);
168 return;
169 }
170
171 while (query.next())
172 {
173 QString name = query.value(0).toString();
174 QString url = query.value(1).toString();
175// QString icon = query.value(2).toString();
176 QDateTime time = MythDate::fromSecsSinceEpoch(query.value(3).toLongLong());
177 bool podcast = query.value(4).toBool();
178 m_newsSites.push_back(new NewsSite(name, url, time, podcast));
179 }
180 std::sort(m_newsSites.begin(), m_newsSites.end(), NewsSite::sortByName);
181
182 for (auto & site : m_newsSites)
183 {
184 auto *item = new MythUIButtonListItem(m_sitesList, site->name());
185 item->SetData(QVariant::fromValue(site));
186
187 connect(site, &NewsSite::finished,
189 }
190
192
193 if (m_nositesText)
194 {
195 if (m_newsSites.empty())
197 else
199 }
200}
201
203{
204 QMutexLocker locker(&m_lock);
205
206 if (!selected)
207 return;
208
209 NewsSite *site = nullptr;
210 NewsArticle article;
211
213 {
214 article = m_articles[selected];
216 site = m_sitesList->GetItemCurrent()->GetData().value<NewsSite*>();
217 }
218 else
219 {
220 site = selected->GetData().value<NewsSite*>();
223 }
224
226 {
227 if (!article.title().isEmpty())
228 {
229
230 if (m_titleText)
231 {
232 QString title = cleanText(article.title());
233 m_titleText->SetText(title);
234 }
235
236 if (m_descText)
237 {
238 QString artText = cleanText(article.description());
239 m_descText->SetText(artText);
240 }
241
242 if (!article.thumbnail().isEmpty())
243 {
245 {
248
251 }
252 }
253 else
254 {
255 if (site && !site->imageURL().isEmpty())
256 {
258 {
261
264 }
265 }
266 else
267 {
270 }
271 }
272
273 if (m_downloadImage)
274 {
275 if (!article.enclosure().isEmpty())
276 {
279 }
280 else
281 {
283 }
284 }
285
287 {
288 if (!article.enclosure().isEmpty())
289 {
292 }
293 else
294 {
296 }
297 }
298
299 if (m_podcastImage)
301 }
302 }
303 else
304 {
305 if (m_downloadImage)
307
310
311 if (m_podcastImage)
313
314 if (site)
315 {
316 if (m_titleText)
317 m_titleText->SetText(site->name());
318
319 if (m_descText)
321
324
325 if (m_podcastImage && site->podcast())
327
328 if (!site->imageURL().isEmpty())
329 {
331 {
334
337 }
338 }
339 }
340 }
341
342 if (m_updatedText)
343 {
344
345 if (site)
346 {
347 QString text(tr("Updated") + " - ");
348 QDateTime updated(site->lastUpdated());
349 if (updated.isValid()) {
350 text += MythDate::toString(site->lastUpdated(),
352 }
353 else
354 {
355 text += tr("Unknown");
356 }
357 m_updatedText->SetText(text);
358 }
359 }
360}
361
362bool MythNews::keyPressEvent(QKeyEvent *event)
363{
365 return true;
366
367 QStringList actions;
368 bool handled = GetMythMainWindow()->TranslateKeyPress("News", event, actions);
369
370 for (int i = 0; i < actions.size() && !handled; i++)
371 {
372 const QString& action = actions[i];
373 handled = true;
374
375 if (action == "RETRIEVENEWS")
377 else if (action == "CANCEL")
379 else if (action == "MENU")
380 ShowMenu();
381 else if (action == "EDIT")
382 ShowEditDialog(true);
383 else if (action == "DELETE")
385 else
386 handled = false;
387 }
388
389 if (!handled && MythScreenType::keyPressEvent(event))
390 handled = true;
391
392 return handled;
393}
394
396{
397 QMutexLocker locker(&m_lock);
398
399 if (m_newsSites.empty())
400 return;
401
402 m_retrieveTimer->stop();
403
404 for (auto & site : m_newsSites)
405 {
406 if (site->timeSinceLastUpdate() > m_updateFreq)
407 site->retrieve();
408 else
409 processAndShowNews(site);
410 }
411
412 m_retrieveTimer->stop();
413 m_retrieveTimer->setSingleShot(false);
415}
416
418{
419 qint64 updated = site->lastUpdated().toSecsSinceEpoch();
420
422 query.prepare("UPDATE newssites SET updated = :UPDATED "
423 "WHERE name = :NAME ;");
424 query.bindValue(":UPDATED", updated);
425 query.bindValue(":NAME", site->name());
426 if (!query.exec() || !query.isActive())
427 MythDB::DBError("news update time", query);
428
429 processAndShowNews(site);
430}
431
433{
434 QMutexLocker locker(&m_lock);
435
436 for (auto & site : m_newsSites)
437 {
438 site->stop();
439 processAndShowNews(site);
440 }
441}
442
444{
445 QMutexLocker locker(&m_lock);
446
447 if (!site)
448 return;
449
450 site->process();
451
453 if (!siteUIItem)
454 return;
455
456 if (site != siteUIItem->GetData().value<NewsSite*>())
457 return;
458
459 QString currItem = m_articlesList->GetValue();
460 int topPos = m_articlesList->GetTopItemPos();
461
463 m_articles.clear();
464
465 NewsArticle::List articles = site->GetArticleList();
466 for (auto & article : articles)
467 {
468 auto *item =
469 new MythUIButtonListItem(m_articlesList, cleanText(article.title()));
470 m_articles[item] = article;
471 }
472
473 if (m_articlesList->MoveToNamedPosition(currItem))
475}
476
478{
479 QMutexLocker locker(&m_lock);
480
481 if (!item || item->GetData().isNull())
482 return;
483
484 auto *site = item->GetData().value<NewsSite*>();
485 if (!site)
486 return;
487
489 m_articles.clear();
490
491 NewsArticle::List articles = site->GetArticleList();
492 for (auto & article : articles)
493 {
494 auto *blitem = new MythUIButtonListItem(m_articlesList, cleanText(article.title()));
495 m_articles[blitem] = article;
496 }
497
498 updateInfoView(item);
499}
500
502{
503 QMutexLocker locker(&m_lock);
504
505 QMap<MythUIButtonListItem*,NewsArticle>::const_iterator it =
506 m_articles.constFind(articlesListItem);
507
508 if (it == m_articles.constEnd())
509 return;
510
511 const NewsArticle& article = *it;
512
513 if (article.articleURL().isEmpty())
514 return;
515
516 if (article.enclosure().isEmpty())
517 {
518 QString cmdUrl(article.articleURL());
519
520 if (m_browser.isEmpty())
521 {
522 ShowOkPopup(tr("No browser command set! MythNews needs MythBrowser to be installed."));
523 return;
524 }
525
526 // display the web page
527 if (m_browser.toLower() == "internal")
528 {
529 GetMythMainWindow()->HandleMedia("WebBrowser", cmdUrl);
530 return;
531 }
532
533 QString cmd = m_browser;
534 cmd.replace("%ZOOM%", m_zoom);
535 cmd.replace("%URL%", cmdUrl);
536 cmd.replace('\'', "%27");
537 cmd.replace("&","\\&");
538 cmd.replace(";","\\;");
539
543 return;
544 }
545
546 playVideo(article);
547}
548
550{
551 QMutexLocker locker(&m_lock);
552
553 NewsSite *site = nullptr;
554
555 if (edit)
556 {
558
559 if (!siteListItem || siteListItem->GetData().isNull())
560 return;
561
562 site = siteListItem->GetData().value<NewsSite*>();
563 }
564
566
567 auto *mythnewseditor = new MythNewsEditor(site, edit, mainStack,
568 "mythnewseditor");
569
570 if (mythnewseditor->Create())
571 {
572 connect(mythnewseditor, &MythScreenType::Exiting, this, &MythNews::loadSites);
573 mainStack->AddScreen(mythnewseditor);
574 }
575 else
576 {
577 delete mythnewseditor;
578 }
579}
580
582{
584
585 auto *mythnewsconfig = new MythNewsConfig(mainStack, "mythnewsconfig");
586
587 if (mythnewsconfig->Create())
588 {
589 connect(mythnewsconfig, &MythScreenType::Exiting, this, &MythNews::loadSites);
590 mainStack->AddScreen(mythnewsconfig);
591 }
592 else
593 {
594 delete mythnewsconfig;
595 }
596}
597
599{
600 QMutexLocker locker(&m_lock);
601
602 QString label = tr("Options");
603
604 MythScreenStack *popupStack =
605 GetMythMainWindow()->GetStack("popup stack");
606
607 m_menuPopup = new MythDialogBox(label, popupStack, "mythnewsmenupopup");
608
609 if (m_menuPopup->Create())
610 {
611 popupStack->AddScreen(m_menuPopup);
612
613 m_menuPopup->SetReturnEvent(this, "options");
614
615 m_menuPopup->AddButton(tr("Manage Feeds"));
616 m_menuPopup->AddButton(tr("Add Feed"));
617 if (!m_newsSites.empty())
618 {
619 m_menuPopup->AddButton(tr("Edit Feed"));
620 m_menuPopup->AddButton(tr("Delete Feed"));
621 }
622 }
623 else
624 {
625 delete m_menuPopup;
626 m_menuPopup = nullptr;
627 }
628}
629
631{
632 QMutexLocker locker(&m_lock);
633
635
636 if (siteUIItem && !siteUIItem->GetData().isNull())
637 {
638 auto *site = siteUIItem->GetData().value<NewsSite*>();
639 if (site)
640 {
641 removeFromDB(site->name());
642 loadSites();
643 }
644 }
645}
646
647// does not need locking
649{
650 GetMythMainWindow()->HandleMedia("Internal", article.enclosure(),
651 article.description(), article.title());
652}
653
654// does not need locking
655void MythNews::customEvent(QEvent *event)
656{
657 if (event->type() == DialogCompletionEvent::kEventType)
658 {
659 auto *dce = dynamic_cast<DialogCompletionEvent*>(event);
660 if (dce == nullptr)
661 return;
662
663 QString resultid = dce->GetId();
664 int buttonnum = dce->GetResult();
665
666 if (resultid == "options")
667 {
668 if (buttonnum == 0)
670 else if (buttonnum == 1)
671 ShowEditDialog(false);
672 else if (buttonnum == 2)
673 ShowEditDialog(true);
674 else if (buttonnum == 3)
676 }
677
678 m_menuPopup = nullptr;
679 }
680}
681
682QString MythNews::cleanText(const QString &text)
683{
684 QString result = text;
685
686 // replace a few HTML characters
687 result.replace("&#8232;", ""); // LSEP
688 result.replace("&#8233;", ""); // PSEP
689 result.replace("&#163;", u8"\u00A3"); // POUND
690 result.replace("&#173;", ""); // ?
691 result.replace("&#8211;", "-"); // EN-DASH
692 result.replace("&#8220;", """"); // LEFT-DOUBLE-QUOTE
693 result.replace("&#8221;", """"); // RIGHT-DOUBLE-QUOTE
694 result.replace("&#8216;", "'"); // LEFT-SINGLE-QUOTE
695 result.replace("&#8217;", "'"); // RIGHT-SINGLE-QUOTE
696 result.replace("&#39;", "'"); // Apostrophe
697
698 // Replace paragraph and break HTML with newlines
699 static const QRegularExpression kHtmlParaStartRE
700 { "<p>", QRegularExpression::CaseInsensitiveOption };
701 static const QRegularExpression kHtmlParaEndRE
702 { "</p>", QRegularExpression::CaseInsensitiveOption };
703 static const QRegularExpression kHtmlBreak1RE
704 { "<(br|)>", QRegularExpression::CaseInsensitiveOption };
705 static const QRegularExpression kHtmlBreak2RE
706 { "<(br|)/>", QRegularExpression::CaseInsensitiveOption };
707 if( result.contains(kHtmlParaEndRE) )
708 {
709 result.replace( kHtmlParaStartRE, "");
710 result.replace( kHtmlParaEndRE, "\n\n");
711 }
712 else
713 {
714 result.replace( kHtmlParaStartRE, "\n\n");
715 result.replace( kHtmlParaEndRE, "");
716 }
717 result.replace( kHtmlBreak2RE, "\n");
718 result.replace( kHtmlBreak1RE, "\n");
719 // These are done instead of simplifyWhitespace
720 // because that function also strips out newlines
721 // Replace tab characters with nothing
722 static const QRegularExpression kTabRE { "\t" };
723 result.replace( kTabRE, "");
724 // Replace double space with single
725 static const QRegularExpression kTwoSpaceRE { " " };
726 result.replace( kTwoSpaceRE, "");
727 // Replace whitespace at beginning of lines with newline
728 static const QRegularExpression kStartingSpaceRE { "\n " };
729 result.replace( kStartingSpaceRE, "\n");
730 // Remove any remaining HTML tags
731 static const QRegularExpression kRemoveHtmlRE(QRegularExpression("</?.+>"));
732 result.remove(kRemoveHtmlRE);
733 result = result.trimmed();
734
735 return result;
736}
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:41
static const Type kEventType
Definition: mythdialogbox.h:56
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:837
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:618
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
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:362
QRecursiveMutex m_lock
Definition: mythnews.h:48
void loadSites(void)
Definition: mythnews.cpp:153
void slotViewArticle(MythUIButtonListItem *articlesListItem)
Definition: mythnews.cpp:501
std::chrono::minutes m_timerTimeout
Definition: mythnews.h:52
MythUIText * m_nositesText
Definition: mythnews.h:63
void slotNewsRetrieved(NewsSite *site)
Definition: mythnews.cpp:417
static QString cleanText(const QString &text)
Definition: mythnews.cpp:682
MythUIImage * m_enclosureImage
Definition: mythnews.h:70
MythUIText * m_updatedText
Definition: mythnews.h:64
static void playVideo(const NewsArticle &article)
Definition: mythnews.cpp:648
MythUIImage * m_thumbnailImage
Definition: mythnews.h:68
void clearSites(void)
Definition: mythnews.cpp:127
void ShowFeedManager() const
Definition: mythnews.cpp:581
void cancelRetrieve(void)
Definition: mythnews.cpp:432
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:443
void ShowMenu(void) override
Definition: mythnews.cpp:598
std::chrono::minutes m_updateFreq
Definition: mythnews.h:53
void deleteNewsSite(void)
Definition: mythnews.cpp:630
void ShowEditDialog(bool edit)
Definition: mythnews.cpp:549
bool Create(void) override
Definition: mythnews.cpp:75
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:43
void slotSiteSelected(MythUIButtonListItem *item)
Definition: mythnews.cpp:477
QTimer * m_retrieveTimer
Definition: mythnews.h:51
void customEvent(QEvent *event) override
Definition: mythnews.cpp:655
QString m_browser
Definition: mythnews.h:56
~MythNews() override
Definition: mythnews.cpp:70
MythUIImage * m_downloadImage
Definition: mythnews.h:69
void slotRetrieveNews(void)
Definition: mythnews.cpp:395
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:903
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:263
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
#define LOC_ERR
Definition: mythnews.cpp:37
@ 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
STL namespace.
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