Tip of the Day (#3118)

* Basic tip of the day with sample widget added

* "Show tips on startup" option added to settings

* tip cycling implemented

* Structure of the tipOfTheDay class and resource created

* tip getter function modified

* Resources added, feature works properly

* clangified

* accidental modification rolled back

* zach cleanup

* tips to spaces; cmake list combined

* cleanup img

* fix copy

* remove TOTD as QObject so we can copy construct it

* prevent mem leaks in dlg

* changed order of 'next' and 'previous' buttons

* Date and tip numbers added; content wraps around

* useless sizepolicy removed

* link support added & clangified

* Initial tips & memory management updates
This commit is contained in:
David Szabo 2018-03-02 09:11:18 +01:00 committed by Zach H
parent 281e52eaa9
commit 312caae062
24 changed files with 498 additions and 2 deletions

View file

@ -67,6 +67,8 @@ GeneralSettingsPage::GeneralSettingsPage()
defaultUrlEdit = new QLineEdit(settingsCache->getPicUrl());
fallbackUrlEdit = new QLineEdit(settingsCache->getPicUrlFallback());
showTipsOnStartup.setChecked(settingsCache->getShowTipsOnStartup());
connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked()));
connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int)));
connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int)));
@ -79,6 +81,7 @@ GeneralSettingsPage::GeneralSettingsPage()
connect(fallbackUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlFallback(QString)));
connect(&defaultUrlRestoreButton, SIGNAL(clicked()), this, SLOT(defaultUrlRestoreButtonClicked()));
connect(&fallbackUrlRestoreButton, SIGNAL(clicked()), this, SLOT(fallbackUrlRestoreButtonClicked()));
connect(&showTipsOnStartup, SIGNAL(clicked(bool)), settingsCache, SLOT(setShowTipsOnStartup(bool)));
setEnabledStatus(settingsCache->getPicDownload());
@ -97,6 +100,7 @@ GeneralSettingsPage::GeneralSettingsPage()
personalGrid->addWidget(&fallbackUrlLabel, 6, 0, 1, 1);
personalGrid->addWidget(fallbackUrlEdit, 6, 1, 1, 1);
personalGrid->addWidget(&fallbackUrlRestoreButton, 6, 2, 1, 1);
personalGrid->addWidget(&showTipsOnStartup, 7, 0);
personalGrid->addWidget(&urlLinkLabel, 7, 1, 1, 1);
personalGrid->addWidget(&clearDownloadedPicsButton, 8, 0, 1, 3);
@ -310,6 +314,7 @@ void GeneralSettingsPage::retranslateUi()
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
defaultUrlRestoreButton.setText(tr("Reset"));
fallbackUrlRestoreButton.setText(tr("Reset"));
showTipsOnStartup.setText(tr("Show tips on startup"));
}
void GeneralSettingsPage::setEnabledStatus(bool status)

View file

@ -82,6 +82,7 @@ private:
QPushButton clearDownloadedPicsButton;
QPushButton defaultUrlRestoreButton;
QPushButton fallbackUrlRestoreButton;
QCheckBox showTipsOnStartup;
};
class AppearanceSettingsPage : public AbstractSettingsPage

View file

@ -0,0 +1,153 @@
#include <QCheckBox>
#include <QDate>
#include <QDebug>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include "dlg_tip_of_the_day.h"
#include "settingscache.h"
#include "tip_of_the_day.h"
#define MIN_TIP_IMAGE_HEIGHT 200
#define MIN_TIP_IMAGE_WIDTH 200
#define MAX_TIP_IMAGE_HEIGHT 300
#define MAX_TIP_IMAGE_WIDTH 300
DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent)
{
successfulInit = false;
QString xmlPath = "theme:tips/tips_of_the_day.xml";
tipDatabase = new TipsOfTheDay(xmlPath, this);
if (tipDatabase->rowCount() == 0) {
return;
}
title = new QLabel();
title->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
tipTextContent = new QLabel();
tipTextContent->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
tipTextContent->setWordWrap(true);
tipTextContent->setTextInteractionFlags(Qt::TextBrowserInteraction);
tipTextContent->setOpenExternalLinks(true);
imageLabel = new QLabel();
imageLabel->setFixedHeight(MAX_TIP_IMAGE_HEIGHT + 50);
imageLabel->setFixedWidth(MAX_TIP_IMAGE_WIDTH + 50);
image = new QPixmap();
date = new QLabel();
date->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
tipNumber = new QLabel();
tipNumber->setAlignment(Qt::AlignCenter);
currentTip = settingsCache->getLastShownTip() + 1;
connect(this, SIGNAL(newTipRequested(int)), this, SLOT(updateTip(int)));
newTipRequested(currentTip);
content = new QVBoxLayout;
content->addWidget(title);
content->addWidget(tipTextContent);
content->addWidget(imageLabel);
content->addWidget(date);
buttonBox = new QDialogButtonBox(Qt::Horizontal);
nextButton = new QPushButton(tr("Next"));
previousButton = new QPushButton(tr("Previous"));
buttonBox->addButton(previousButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(nextButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(QDialogButtonBox::Ok);
buttonBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(nextButton, SIGNAL(clicked()), this, SLOT(nextClicked()));
connect(previousButton, SIGNAL(clicked()), this, SLOT(previousClicked()));
showTipsOnStartupCheck = new QCheckBox("Show tips on startup");
showTipsOnStartupCheck->setChecked(true);
connect(showTipsOnStartupCheck, SIGNAL(clicked(bool)), settingsCache, SLOT(setShowTipsOnStartup(bool)));
buttonBar = new QHBoxLayout();
buttonBar->addWidget(showTipsOnStartupCheck);
buttonBar->addWidget(tipNumber);
buttonBar->addWidget(buttonBox);
mainLayout = new QVBoxLayout;
mainLayout->addLayout(content);
mainLayout->addLayout(buttonBar);
setLayout(mainLayout);
setWindowTitle(tr("Tip of the Day"));
setMinimumWidth(500);
setMinimumHeight(300);
successfulInit = true;
}
DlgTipOfTheDay::~DlgTipOfTheDay()
{
tipDatabase->deleteLater();
title->deleteLater();
tipTextContent->deleteLater();
imageLabel->deleteLater();
tipNumber->deleteLater();
showTipsOnStartupCheck->deleteLater();
content->deleteLater();
mainLayout->deleteLater();
buttonBox->deleteLater();
nextButton->deleteLater();
previousButton->deleteLater();
buttonBar->deleteLater();
delete image;
}
void DlgTipOfTheDay::nextClicked()
{
emit newTipRequested(currentTip + 1);
}
void DlgTipOfTheDay::previousClicked()
{
emit newTipRequested(currentTip - 1);
}
void DlgTipOfTheDay::updateTip(int tipId)
{
QString titleText, contentText, imagePath;
if (tipId < 0) {
tipId = tipDatabase->rowCount() - 1;
} else if (tipId >= tipDatabase->rowCount()) {
tipId = tipId % tipDatabase->rowCount();
}
TipOfTheDay tip = tipDatabase->getTip(tipId);
titleText = tip.getTitle();
contentText = tip.getContent();
imagePath = tip.getImagePath();
title->setText("<h2>" + titleText + "</h2>");
tipTextContent->setText(contentText);
if (!image->load(imagePath)) {
qDebug() << "Image failed to load from" << imagePath;
imageLabel->clear();
} else {
int h = std::min(std::max(image->height(), MIN_TIP_IMAGE_HEIGHT), MAX_TIP_IMAGE_HEIGHT);
int w = std::min(std::max(imageLabel->width(), MIN_TIP_IMAGE_WIDTH), MAX_TIP_IMAGE_WIDTH);
imageLabel->setPixmap(image->scaled(h, w, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
date->setText("<i>Tip added on: " + tip.getDate().toString("yyyy.MM.dd") + "</i>");
tipNumber->setText("Tip " + QString::number(tipId + 1) + " / " + QString::number(tipDatabase->rowCount()));
currentTip = static_cast<unsigned int>(tipId);
settingsCache->setLastShownTip(currentTip);
}
void DlgTipOfTheDay::resizeEvent(QResizeEvent *event)
{
int h = imageLabel->height();
int w = imageLabel->width();
imageLabel->setPixmap(image->scaled(w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation));
QWidget::resizeEvent(event);
}

View file

@ -0,0 +1,48 @@
#ifndef DLG_TIPOFDAY_H
#define DLG_TIPOFDAY_H
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
class QLabel;
class QPushButton;
class QCheckBox;
class TipsOfTheDay;
class DlgTipOfTheDay : public QDialog
{
Q_OBJECT
public:
explicit DlgTipOfTheDay(QWidget *parent = nullptr);
~DlgTipOfTheDay() override;
bool successfulInit;
signals:
void newTipRequested(int tipId);
protected:
void resizeEvent(QResizeEvent *event) override;
private:
unsigned int currentTip;
TipsOfTheDay *tipDatabase;
QLabel *title, *tipTextContent, *imageLabel, *tipNumber, *date;
QCheckBox *showTipsOnStartupCheck;
QPixmap *image;
QVBoxLayout *content, *mainLayout;
QDialogButtonBox *buttonBox;
QPushButton *nextButton, *previousButton;
QHBoxLayout *buttonBar;
private slots:
void nextClicked();
void previousClicked();
void updateTip(int tipId);
};
#endif

View file

@ -22,6 +22,7 @@
#include "QtNetwork/QNetworkInterface"
#include "carddatabase.h"
#include "dlg_settings.h"
#include "dlg_tip_of_the_day.h"
#include "featureset.h"
#include "logger.h"
#include "pixmapgenerator.h"
@ -137,6 +138,11 @@ int main(int argc, char *argv[])
ui.show();
qDebug("main(): ui.show() finished");
DlgTipOfTheDay tip;
if (settingsCache->getShowTipsOnStartup() && tip.successfulInit) {
tip.show();
}
app.setAttribute(Qt::AA_UseHighDpiPixmaps);
app.exec();

View file

@ -220,8 +220,8 @@ void SetsModel::sort(int column, Qt::SortOrder order)
void SetsModel::save(CardDatabase *db)
{
// order
for (unsigned int i = 0; i < sets.size(); i++)
sets[i]->setSortKey(i + 1);
for (int i = 0; i < sets.size(); i++)
sets[i]->setSortKey(static_cast<unsigned int>(i + 1));
// enabled sets
for (const CardSetPtr &set : sets)

View file

@ -179,6 +179,10 @@ SettingsCache::SettingsCache()
lang = settings->value("personal/lang").toString();
keepalive = settings->value("personal/keepalive", 5).toInt();
// tip of the day settings
showTipsOnStartup = settings->value("tipOfDay/showTips", true).toBool();
lastShownTip = settings->value("tipOfDay/lastShown", -1).toInt();
deckPath = getSafeConfigPath("paths/decks", dataPath + "/decks/");
replaysPath = getSafeConfigPath("paths/replays", dataPath + "/replays/");
picsPath = getSafeConfigPath("paths/pics", dataPath + "/pics/");
@ -335,6 +339,18 @@ void SettingsCache::setLang(const QString &_lang)
emit langChanged();
}
void SettingsCache::setShowTipsOnStartup(bool _showTipsOnStartup)
{
showTipsOnStartup = _showTipsOnStartup;
settings->setValue("tipOfDay/showTips", showTipsOnStartup);
}
void SettingsCache::setLastShownTip(int _lastShownTip)
{
lastShownTip = _lastShownTip;
settings->setValue("tipOfDay/lastShown", lastShownTip);
}
void SettingsCache::setDeckPath(const QString &_deckPath)
{
deckPath = _deckPath;

View file

@ -67,6 +67,8 @@ private:
QString deckPath, replaysPath, picsPath, customPicsPath, cardDatabasePath, customCardDatabasePath,
spoilerDatabasePath, tokenDatabasePath, themeName;
bool notifyAboutUpdates;
bool showTipsOnStartup;
unsigned int lastShownTip;
bool mbDownloadSpoilers;
int updateReleaseChannel;
int maxFontSize;
@ -199,6 +201,14 @@ public:
{
return notifyAboutUpdates;
}
bool getShowTipsOnStartup() const
{
return showTipsOnStartup;
}
unsigned int getLastShownTip() const
{
return lastShownTip;
}
ReleaseChannel *getUpdateReleaseChannel() const
{
return releaseChannels.at(updateReleaseChannel);
@ -433,6 +443,8 @@ public slots:
void setMainWindowGeometry(const QByteArray &_mainWindowGeometry);
void setTokenDialogGeometry(const QByteArray &_tokenDialog);
void setLang(const QString &_lang);
void setShowTipsOnStartup(bool _showTipsOnStartup);
void setLastShownTip(int _lastShowTip);
void setDeckPath(const QString &_deckPath);
void setReplaysPath(const QString &_replaysPath);
void setPicsPath(const QString &_picsPath);

View file

@ -0,0 +1,99 @@
#include <QDate>
#include <QFile>
#include <QMessageBox>
#include <QTextStream>
#include <QXmlStreamReader>
#include <utility>
#include "tip_of_the_day.h"
#define TIPDDBMODEL_COLUMNS 3
TipOfTheDay::TipOfTheDay(QString _title, QString _content, QString _imagePath, QDate _date)
: title(std::move(_title)), content(std::move(_content)), imagePath(std::move(_imagePath)), date(_date)
{
}
TipsOfTheDay::TipsOfTheDay(QString xmlPath, QObject *parent) : QAbstractListModel(parent)
{
tipList = new QList<TipOfTheDay>;
QFile xmlFile(xmlPath);
QTextStream errorStream(stderr);
if (!QFile::exists(xmlPath)) {
errorStream << tr("File does not exist.\n");
return;
} else if (!xmlFile.open(QIODevice::ReadOnly)) {
errorStream << tr("Failed to open file.\n");
return;
}
QXmlStreamReader reader(&xmlFile);
while (!reader.atEnd()) {
if (reader.readNext() == QXmlStreamReader::EndElement) {
break;
}
if (reader.name() == "tip") {
QString title, content, imagePath;
QDate date;
reader.readNext();
while (!reader.atEnd()) {
if (reader.readNext() == QXmlStreamReader::EndElement) {
break;
}
if (reader.name() == "title") {
title = reader.readElementText();
} else if (reader.name() == "text") {
content = reader.readElementText();
} else if (reader.name() == "image") {
imagePath = "theme:tips/images/" + reader.readElementText();
} else if (reader.name() == "date") {
date = QDate::fromString(reader.readElementText(), Qt::ISODate);
} else {
// unkown element, do nothing
}
}
tipList->append(TipOfTheDay(title, content, imagePath, date));
}
}
}
TipsOfTheDay::~TipsOfTheDay()
{
delete tipList;
}
QVariant TipsOfTheDay::data(const QModelIndex &index, int /*role*/) const
{
if (!index.isValid() || index.row() >= tipList->size() || index.column() >= TIPDDBMODEL_COLUMNS)
return QVariant();
TipOfTheDay tip = tipList->at(index.row());
switch (index.column()) {
case TitleColumn:
return tip.getTitle();
case ContentColumn:
return tip.getContent();
case ImagePathColumn:
return tip.getImagePath();
case DateColumn:
return tip.getDate();
default:
return QVariant();
}
}
TipOfTheDay TipsOfTheDay::getTip(int tipId)
{
return tipList->at(tipId);
}
int TipsOfTheDay::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return tipList->size();
}

View file

@ -0,0 +1,54 @@
#ifndef TIP_OF_DAY_H
#define TIP_OF_DAY_H
#include <QAbstractListModel>
class TipOfTheDay
{
public:
explicit TipOfTheDay(QString _title, QString _content, QString _imagePath, QDate _date);
QString getTitle() const
{
return title;
}
QString getContent() const
{
return content;
}
QString getImagePath() const
{
return imagePath;
}
QDate getDate() const
{
return date;
}
private:
QString title, content, imagePath;
QDate date;
};
class TipsOfTheDay : public QAbstractListModel
{
Q_OBJECT
public:
enum Columns
{
TitleColumn,
ContentColumn,
ImagePathColumn,
DateColumn,
};
explicit TipsOfTheDay(QString xmlPath, QObject *parent = nullptr);
~TipsOfTheDay() override;
TipOfTheDay getTip(int tipId);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
private:
QList<TipOfTheDay> *tipList;
};
#endif