Refactoring of tooltips.

This commit is contained in:
Martin Rotter 2017-10-05 11:10:16 +02:00
parent ab4bb279b7
commit 25c8725029
20 changed files with 162 additions and 124 deletions

View File

@ -57,7 +57,6 @@
#include <QProgressBar>
#include <QSplitter>
#include <QStatusBar>
#include <QThread>
#include <QToolBar>
#include <QToolButton>
#include <QVBoxLayout>

View File

@ -108,9 +108,6 @@ int main(int argc, char* argv[]) {
Application::setOrganizationDomain(APP_URL);
Application::setWindowIcon(QIcon(APP_ICON_PATH));
// Load activated accounts.
qApp->feedReader()->feedsModel()->loadActivatedServiceAccounts();
// Setup single-instance behavior.
QObject::connect(&application, &Application::messageReceived, &application, &Application::processExecutionMessage);
qDebug().nospace() << "Creating main application form in thread: \'" << QThread::currentThreadId() << "\'.";
@ -139,6 +136,9 @@ int main(int argc, char* argv[]) {
qApp->showTrayIcon();
}
// Load activated accounts.
qApp->feedReader()->feedsModel()->loadActivatedServiceAccounts();
if (qApp->isFirstRun() || qApp->isFirstRun(APP_VERSION)) {
qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1.\n\nPlease, check NEW stuff included in this\n"
"version by clicking this popup notification.").arg(APP_LONG_NAME),

View File

@ -69,12 +69,26 @@ OAuth2Service::OAuth2Service(QString authUrl, QString tokenUrl, QString clientId
}
QString OAuth2Service::bearer() {
if (login()) {
return QString("Bearer %1").arg(m_accessToken);
}
else {
if (!isFullyLoggedIn()) {
qApp->showGuiMessage(tr("Inoreader: you have to login first"),
tr("Click here to login."),
QSystemTrayIcon::Critical,
nullptr, false,
[this]() {
login();
});
return QString();
}
else {
return QString("Bearer %1").arg(m_accessToken);
}
}
bool OAuth2Service::isFullyLoggedIn() const {
bool is_expiration_valid = m_tokensExpireIn > QDateTime::currentDateTime();
bool do_tokens_exist = !m_refreshToken.isEmpty() && !m_accessToken.isEmpty();
return is_expiration_valid && do_tokens_exist;
}
void OAuth2Service::setOAuthTokenGrantType(QString grant_type) {
@ -124,15 +138,15 @@ void OAuth2Service::refreshAccessToken(QString refresh_token) {
.arg(refresh_token)
.arg("refresh_token");
qApp->showGuiMessage(tr("Logging in via OAuth 2.0..."),
tr("Refreshing login tokens for '%1'...").arg(m_tokenUrl.toString()),
QSystemTrayIcon::MessageIcon::Information);
m_networkManager.post(networkRequest, content.toUtf8());
}
void OAuth2Service::cleanTokens() {
m_refreshToken = m_accessToken = QString();
}
void OAuth2Service::tokenRequestFinished(QNetworkReply* networkReply) {
QJsonDocument jsonDocument = QJsonDocument::fromJson(networkReply->readAll());
void OAuth2Service::tokenRequestFinished(QNetworkReply* network_reply) {
QJsonDocument jsonDocument = QJsonDocument::fromJson(network_reply->readAll());
QJsonObject rootObject = jsonDocument.object();
qDebug() << "Token response:";
@ -142,8 +156,7 @@ void OAuth2Service::tokenRequestFinished(QNetworkReply* networkReply) {
QString error = rootObject.value("error").toString();
QString error_description = rootObject.value("error_description").toString();
cleanTokens();
login();
logout();
emit tokensRetrieveError(error, error_description);
}
@ -160,7 +173,7 @@ void OAuth2Service::tokenRequestFinished(QNetworkReply* networkReply) {
emit tokensReceived(m_accessToken, m_refreshToken, rootObject.value("expires_in").toInt());
}
networkReply->deleteLater();
network_reply->deleteLater();
}
QString OAuth2Service::accessToken() const {
@ -248,8 +261,13 @@ void OAuth2Service::retrieveAuthCode() {
connect(&login_page, &OAuthLogin::authGranted, this, &OAuth2Service::authCodeObtained);
connect(&login_page, &OAuthLogin::authRejected, [this]() {
cleanTokens();
logout();
emit authFailed();
});
qApp->showGuiMessage(tr("Logging in via OAuth 2.0..."),
tr("Requesting access authorization for '%1'...").arg(m_authUrl),
QSystemTrayIcon::MessageIcon::Information);
login_page.login(auth_url, m_redirectUrl);
}

View File

@ -52,7 +52,13 @@ class OAuth2Service : public QObject {
explicit OAuth2Service(QString authUrl, QString tokenUrl, QString clientId,
QString clientSecret, QString scope, QObject* parent = 0);
// Returns bearer HTTP header value.
// NOTE: Only call this if isFullyLoggedIn()
// returns true. If isFullyLoggedIn() returns
// false, then you must call login() on
// main GUI thread.
QString bearer();
bool isFullyLoggedIn() const;
void setOAuthTokenGrantType(QString grant_type);
QString oAuthTokenGrantType();
@ -95,12 +101,14 @@ class OAuth2Service : public QObject {
// access token is made.
// Returns true, if user is already logged in (final state).
// Returns false, if user is NOT logged in (asynchronous flow).
//
// NOTE: This can be called ONLY on main GUI thread,
// because widgets may be displayed.
bool login();
void logout();
private slots:
void cleanTokens();
void tokenRequestFinished(QNetworkReply* networkReply);
void tokenRequestFinished(QNetworkReply* network_reply);
private:
QDateTime m_tokensExpireIn;

View File

@ -81,19 +81,6 @@ QList<Message> Feed::undeletedMessages() const {
QVariant Feed::data(int column, int role) const {
switch (role) {
case Qt::ToolTipRole:
if (column == FDS_MODEL_TITLE_INDEX) {
//: Tooltip for feed.
return tr("%1"
"%2\n\n"
"Auto-update status: %3").arg(title(),
description().isEmpty() ? QString() : QString('\n') + description(),
getAutoUpdateStatusDescription());
}
else {
return RootItem::data(column, role);
}
case Qt::ForegroundRole:
switch (status()) {
case NewMessages:
@ -308,3 +295,27 @@ QString Feed::getAutoUpdateStatusDescription() const {
return auto_update_string;
}
QString Feed::getStatusDescription() const {
switch (m_status) {
case Status::Normal:
return tr("no errors");
case Status::NewMessages:
return tr("has new messages");
case Status::AuthError:
return tr("authentication error");
case Status::NetworkError:
return tr("network error");
default:
return tr("unspecified error");
}
}
QString Feed::additionalTooltip() const {
return tr("Auto-update status: %1\n"
"Status: %2").arg(getAutoUpdateStatusDescription(), getStatusDescription());
}

View File

@ -46,8 +46,9 @@ class Feed : public RootItem, public QRunnable {
Normal = 0,
NewMessages = 1,
NetworkError = 2,
ParsingError = 3,
OtherError = 4
AuthError = 3,
ParsingError = 4,
OtherError = 5
};
// Constructors.
@ -58,6 +59,8 @@ class Feed : public RootItem, public QRunnable {
QList<Message> undeletedMessages() const;
QString additionalTooltip() const;
int countOfAllMessages() const;
int countOfUnreadMessages() const;
@ -93,6 +96,7 @@ class Feed : public RootItem, public QRunnable {
protected:
QString getAutoUpdateStatusDescription() const;
QString getStatusDescription() const;
signals:
void messagesObtained(QList<Message> messages, bool error_during_obtaining);

View File

@ -39,6 +39,10 @@ RecycleBin::RecycleBin(RootItem* parent_item) : RootItem(parent_item), m_totalCo
RecycleBin::~RecycleBin() {}
QString RecycleBin::additionalTooltip() const {
return tr("%n deleted message(s).", 0, countOfAllMessages());
}
int RecycleBin::countOfUnreadMessages() const {
return m_unreadCount;
}
@ -60,16 +64,6 @@ void RecycleBin::updateCounts(bool update_total_count) {
}
}
QVariant RecycleBin::data(int column, int role) const {
switch (role) {
case Qt::ToolTipRole:
return tr("Recycle bin\n\n%1").arg(tr("%n deleted message(s).", 0, countOfAllMessages()));
default:
return RootItem::data(column, role);
}
}
QList<QAction*> RecycleBin::contextMenu() {
if (m_contextMenu.isEmpty()) {
QAction* restore_action = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")),

View File

@ -28,7 +28,7 @@ class RecycleBin : public RootItem {
explicit RecycleBin(RootItem* parent_item = nullptr);
virtual ~RecycleBin();
QVariant data(int column, int role) const;
QString additionalTooltip() const;
QList<QAction*> contextMenu();
QList<Message> undeletedMessages() const;

View File

@ -58,6 +58,10 @@ QString RootItem::hashCode() const {
QString::number(id());
}
QString RootItem::additionalTooltip() const {
return QString();
}
QList<QAction*> RootItem::contextMenu() {
return QList<QAction*>();
}
@ -142,7 +146,13 @@ QVariant RootItem::data(int column, int role) const {
QString tool_tip = m_title;
if (!m_description.isEmpty()) {
tool_tip += QL1S("\n\n") + m_description;
tool_tip += QL1S("\n") + m_description;
}
QString extra_tooltip = additionalTooltip();
if (!extra_tooltip.isEmpty()) {
tool_tip += QL1S("\n\n") + extra_tooltip;
}
return tool_tip;

View File

@ -71,6 +71,7 @@ class RootItem : public QObject {
virtual ~RootItem();
virtual QString hashCode() const;
virtual QString additionalTooltip() const;
// Returns list of specific actions which can be done with the item.
// Do not include general actions here like actions: Mark as read, Update, ...

View File

@ -32,7 +32,14 @@ InoreaderServiceRoot* InoreaderFeed::serviceRoot() const {
}
QList<Message> InoreaderFeed::obtainNewMessages(bool* error_during_obtaining) {
QList<Message> messages = serviceRoot()->network()->messages(customId(), error_during_obtaining);
Feed::Status error;
QList<Message> messages = serviceRoot()->network()->messages(customId(), error);
setStatus(error);
if (error == Feed::Status::NetworkError || error == Feed::Status::AuthError) {
*error_during_obtaining = true;
}
return messages;
}

View File

@ -120,9 +120,10 @@ void InoreaderServiceRoot::start(bool freshly_activated) {
Q_UNUSED(freshly_activated)
loadFromDatabase();
m_network->oauth()->login();
loadCacheFromFile(accountId());
m_network->oauth()->login();
if (childCount() <= 1) {
syncIn();
}

View File

@ -66,17 +66,18 @@ void InoreaderNetworkFactory::setBatchSize(int batch_size) {
}
void InoreaderNetworkFactory::initializeOauth() {
connect(m_oauth2, &OAuth2Service::tokensRetrieveError, [](QString error, QString error_description) {
Q_UNUSED(error)
qApp->showGuiMessage("Authentication error - Inoreader", error_description, QSystemTrayIcon::Critical);
});
connect(m_oauth2, &OAuth2Service::tokensRetrieveError, this, &InoreaderNetworkFactory::onTokensError);
connect(m_oauth2, &OAuth2Service::authFailed, this, &InoreaderNetworkFactory::onAuthFailed);
connect(m_oauth2, &OAuth2Service::tokensReceived, [this](QString access_token, QString refresh_token, int expires_in) {
Q_UNUSED(expires_in)
if (m_service != nullptr && !access_token.isEmpty() && !refresh_token.isEmpty()) {
QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings);
DatabaseQueries::storeNewInoreaderTokens(database, refresh_token, m_service->accountId());
qApp->showGuiMessage(tr("Logged in successfully"),
tr("Your login to Inoreader was authorized."),
QSystemTrayIcon::MessageIcon::Information);
}
});
}
@ -121,13 +122,14 @@ RootItem* InoreaderNetworkFactory::feedsCategories(bool obtain_icons) {
return decodeFeedCategoriesData(category_data, feed_data, obtain_icons);
}
QList<Message> InoreaderNetworkFactory::messages(const QString& stream_id, bool* is_error) {
QList<Message> InoreaderNetworkFactory::messages(const QString& stream_id, Feed::Status& error) {
Downloader downloader;
QEventLoop loop;
QString target_url = INOREADER_API_FEED_CONTENTS;
QString bearer = m_oauth2->bearer().toLocal8Bit();
if (bearer.isEmpty()) {
error == Feed::Status::AuthError;
return QList<Message>();
}
@ -140,12 +142,13 @@ QList<Message> InoreaderNetworkFactory::messages(const QString& stream_id, bool*
loop.exec();
if (downloader.lastOutputError() != QNetworkReply::NetworkError::NoError) {
*is_error = true;
error == Feed::Status::NetworkError;
return QList<Message>();
}
else {
QString messages_data = downloader.lastOutputData();
error == Feed::Status::Normal;
return decodeMessages(messages_data, stream_id);
}
}
@ -204,6 +207,28 @@ void InoreaderNetworkFactory::markMessagesRead(RootItem::ReadStatus status, cons
void InoreaderNetworkFactory::markMessagesStarred(RootItem::Importance importance, const QStringList& custom_ids) {}
void InoreaderNetworkFactory::onTokensError(const QString& error, const QString& error_description) {
Q_UNUSED(error)
qApp->showGuiMessage(tr("Inoreader: authentication error"),
tr("Click this to login again. Error is: '%1'").arg(error_description),
QSystemTrayIcon::Critical,
nullptr, false,
[this]() {
m_oauth2->login();
});
}
void InoreaderNetworkFactory::onAuthFailed() {
qApp->showGuiMessage(tr("Inoreader: authorization denied"),
tr("Click this to login again."),
QSystemTrayIcon::Critical,
nullptr, false,
[this]() {
m_oauth2->login();
});
}
QList<Message> InoreaderNetworkFactory::decodeMessages(const QString& messages_json_data, const QString& stream_id) {
QList<Message> messages;
QJsonArray json = QJsonDocument::fromJson(messages_json_data.toUtf8()).object()["items"].toArray();

View File

@ -23,6 +23,7 @@
#include "core/message.h"
#include "services/abstract/feed.h"
#include "services/abstract/rootitem.h"
#include <QNetworkReply>
@ -53,10 +54,14 @@ class InoreaderNetworkFactory : public QObject {
// Returned items do not have primary IDs assigned.
RootItem* feedsCategories(bool obtain_icons);
QList<Message> messages(const QString& stream_id, bool* is_error);
QList<Message> messages(const QString& stream_id, Feed::Status& error);
void markMessagesRead(RootItem::ReadStatus status, const QStringList& custom_ids);
void markMessagesStarred(RootItem::Importance importance, const QStringList& custom_ids);
private slots:
void onTokensError(const QString& error, const QString& error_description);
void onAuthFailed();
private:
QList<Message> decodeMessages(const QString& messages_json_data, const QString& stream_id);
RootItem* decodeFeedCategoriesData(const QString& categories, const QString& feeds, bool obtain_icons);

View File

@ -71,6 +71,14 @@ QList<QAction*> StandardFeed::contextMenu() {
return serviceRoot()->getContextMenuForFeed(this);
}
QString StandardFeed::additionalTooltip() const {
return Feed::additionalTooltip() + tr("\nNetwork status: %1\n"
"Encoding: %2\n"
"Type: %3").arg(NetworkFactory::networkErrorText(m_networkError),
encoding(),
StandardFeed::typeToString(type()));
}
bool StandardFeed::canBeEdited() const {
return true;
}
@ -99,31 +107,6 @@ bool StandardFeed::deleteViaGui() {
}
}
QVariant StandardFeed::data(int column, int role) const {
switch (role) {
case Qt::ToolTipRole:
if (column == FDS_MODEL_TITLE_INDEX) {
//: Tooltip for feed.
return tr("%1 (%2)"
"%3\n\n"
"Network status: %6\n"
"Encoding: %4\n"
"Auto-update status: %5").arg(title(),
StandardFeed::typeToString(type()),
description().isEmpty() ? QString() : QString('\n') + description(),
encoding(),
getAutoUpdateStatusDescription(),
NetworkFactory::networkErrorText(m_networkError));
}
else {
return Feed::data(column, role);
}
default:
return Feed::data(column, role);
}
}
QString StandardFeed::typeToString(StandardFeed::Type type) {
switch (type) {
case Atom10:
@ -452,7 +435,6 @@ QList<Message> StandardFeed::obtainNewMessages(bool* error_during_obtaining) {
return QList<Message>();
}
else if (status() != NewMessages) {
setStatus(Normal);
*error_during_obtaining = false;
}

View File

@ -53,14 +53,14 @@ class StandardFeed : public Feed {
QList<QAction*> contextMenu();
QString additionalTooltip() const;
bool canBeEdited() const;
bool canBeDeleted() const;
bool editViaGui();
bool deleteViaGui();
QVariant data(int column, int role) const;
// Obtains data related to this feed.
Qt::ItemFlags additionalFlags() const;
bool performDragDropChange(RootItem* target_item);

View File

@ -141,21 +141,6 @@ void StandardServiceRoot::addNewFeed(const QString& url) {
qApp->feedUpdateLock()->unlock();
}
QVariant StandardServiceRoot::data(int column, int role) const {
switch (role) {
case Qt::ToolTipRole:
if (column == FDS_MODEL_TITLE_INDEX) {
return tr("This is service account for standard RSS/RDF/ATOM feeds.\n\nAccount ID: %1").arg(accountId());
}
else {
return ServiceRoot::data(column, role);
}
default:
return ServiceRoot::data(column, role);
}
}
Qt::ItemFlags StandardServiceRoot::additionalFlags() const {
return Qt::ItemIsDropEnabled;
}

View File

@ -48,7 +48,6 @@ class StandardServiceRoot : public ServiceRoot {
bool supportsFeedAdding() const;
bool supportsCategoryAdding() const;
QVariant data(int column, int role) const;
Qt::ItemFlags additionalFlags() const;
// Returns menu to be shown in "Services -> service" menu.

View File

@ -125,28 +125,6 @@ bool TtRssServiceRoot::canBeDeleted() const {
return true;
}
QVariant TtRssServiceRoot::data(int column, int role) const {
switch (role) {
case Qt::ToolTipRole:
if (column == FDS_MODEL_TITLE_INDEX) {
return tr("Tiny Tiny RSS\n\nAccount ID: %3\nUsername: %1\nServer: %2\n"
"Last error: %4\nLast login on: %5").arg(m_network->username(),
m_network->url(),
QString::number(accountId()),
NetworkFactory::networkErrorText(m_network->lastError()),
m_network->lastLoginTime().isValid() ?
m_network->lastLoginTime().toString(Qt::DefaultLocaleShortDate) :
QSL("-"));
}
else {
return ServiceRoot::data(column, role);
}
default:
return ServiceRoot::data(column, role);
}
}
void TtRssServiceRoot::saveAllCachedData() {
QPair<QMap<RootItem::ReadStatus, QStringList>, QMap<RootItem::Importance, QList<Message>>> msgCache = takeMessageCache();
QMapIterator<RootItem::ReadStatus, QStringList> i(msgCache.first);
@ -193,6 +171,16 @@ QList<QAction*> TtRssServiceRoot::serviceMenu() {
return m_serviceMenu;
}
QString TtRssServiceRoot::additionalTooltip() const {
return tr("Username: %1\nServer: %2\n"
"Last error: %3\nLast login on: %4").arg(m_network->username(),
m_network->url(),
NetworkFactory::networkErrorText(m_network->lastError()),
m_network->lastLoginTime().isValid() ?
m_network->lastLoginTime().toString(Qt::DefaultLocaleShortDate) :
QSL("-"));
}
TtRssNetworkFactory* TtRssServiceRoot::network() const {
return m_network;
}

View File

@ -44,9 +44,10 @@ class TtRssServiceRoot : public ServiceRoot, public CacheForServiceRoot {
bool deleteViaGui();
bool supportsFeedAdding() const;
bool supportsCategoryAdding() const;
QVariant data(int column, int role) const;
QList<QAction*> serviceMenu();
QString additionalTooltip() const;
void saveAllCachedData();
// Access to network.