Shitload of changes, save it.

Also note that this introduces some SQL changes for metadata version 2 which is still unpublished but was introduced some commits ago, therefore individual "devbuild"s are NOT cross-compatible betweem each other.
This commit is contained in:
Martin Rotter 2022-03-01 14:45:20 +01:00
parent 83f2da43fa
commit 7f5d1473a3
38 changed files with 348 additions and 107 deletions

View File

@ -26,7 +26,7 @@
<url type="donation">https://github.com/sponsors/martinrotter</url>
<content_rating type="oars-1.1" />
<releases>
<release version="4.1.2" date="2022-02-28"/>
<release version="4.1.2" date="2022-03-01"/>
</releases>
<content_rating type="oars-1.0">
<content_attribute id="violence-cartoon">none</content_attribute>

View File

@ -7,7 +7,9 @@
<file>./graphics/Breeze/categories/32/applications-science.svg</file>
<file>./graphics/Breeze/categories/32/applications-system.svg</file>
<file>./graphics/Breeze/actions/32/arrow-down.svg</file>
<file>./graphics/Breeze/actions/32/arrow-down-double.svg</file>
<file>./graphics/Breeze/actions/32/arrow-up.svg</file>
<file>./graphics/Breeze/actions/32/arrow-up-double.svg</file>
<file>./graphics/Breeze/actions/32/call-start.svg</file>
<file>./graphics/Breeze/status/64/dialog-error.svg</file>
<file>./graphics/Breeze/status/64/dialog-information.svg</file>
@ -30,12 +32,11 @@
<file>./graphics/Breeze/places/96/folder.svg</file>
<file>./graphics/Breeze/actions/22/format-indent-more.svg</file>
<file>./graphics/Breeze/actions/22/format-justify-fill.svg</file>
<file>./graphics/Breeze/actions/32/go-down.svg</file>
<file>./graphics/Breeze/actions/22/format-text-bold.svg</file>
<file>./graphics/Breeze/actions/64/go-home.svg</file>
<file>./graphics/Breeze/actions/32/go-jump.svg</file>
<file>./graphics/Breeze/actions/32/go-next.svg</file>
<file>./graphics/Breeze/actions/32/go-previous.svg</file>
<file>./graphics/Breeze/actions/32/go-up.svg</file>
<file>./graphics/Breeze/actions/22/gtk-edit.svg</file>
<file>./graphics/Breeze/actions/32/help-about.svg</file>
<file>./graphics/Breeze/actions/22/help-contents.svg</file>
@ -75,7 +76,9 @@
<file>./graphics/Breeze Dark/categories/32/applications-science.svg</file>
<file>./graphics/Breeze Dark/categories/32/applications-system.svg</file>
<file>./graphics/Breeze Dark/actions/32/arrow-down.svg</file>
<file>./graphics/Breeze Dark/actions/32/arrow-down-double.svg</file>
<file>./graphics/Breeze Dark/actions/32/arrow-up.svg</file>
<file>./graphics/Breeze Dark/actions/32/arrow-up-double.svg</file>
<file>./graphics/Breeze Dark/actions/32/call-start.svg</file>
<file>./graphics/Breeze Dark/status/64/dialog-error.svg</file>
<file>./graphics/Breeze Dark/status/64/dialog-information.svg</file>
@ -98,12 +101,11 @@
<file>./graphics/Breeze Dark/places/96/folder.svg</file>
<file>./graphics/Breeze Dark/actions/22/format-indent-more.svg</file>
<file>./graphics/Breeze Dark/actions/22/format-justify-fill.svg</file>
<file>./graphics/Breeze Dark/actions/32/go-down.svg</file>
<file>./graphics/Breeze Dark/actions/22/format-text-bold.svg</file>
<file>./graphics/Breeze Dark/actions/64/go-home.svg</file>
<file>./graphics/Breeze Dark/actions/32/go-jump.svg</file>
<file>./graphics/Breeze Dark/actions/32/go-next.svg</file>
<file>./graphics/Breeze Dark/actions/32/go-previous.svg</file>
<file>./graphics/Breeze Dark/actions/32/go-up.svg</file>
<file>./graphics/Breeze Dark/actions/22/gtk-edit.svg</file>
<file>./graphics/Breeze Dark/actions/32/help-about.svg</file>
<file>./graphics/Breeze Dark/actions/22/help-contents.svg</file>
@ -166,12 +168,11 @@
<file>./graphics/Faenza/places/64/folder.png</file>
<file>./graphics/Faenza/actions/64/format-indent-more.png</file>
<file>./graphics/Faenza/actions/64/format-justify-fill.png</file>
<file>./graphics/Faenza/actions/64/go-down.png</file>
<file>./graphics/Faenza/actions/64/format-text-bold.png</file>
<file>./graphics/Faenza/actions/64/go-home.png</file>
<file>./graphics/Faenza/actions/64/go-jump.png</file>
<file>./graphics/Faenza/actions/64/go-next.png</file>
<file>./graphics/Faenza/actions/64/go-previous.png</file>
<file>./graphics/Faenza/actions/64/go-up.png</file>
<file>./graphics/Faenza/actions/64/gtk-edit.png</file>
<file>./graphics/Faenza/actions/64/help-about.png</file>
<file>./graphics/Faenza/actions/64/help-contents.png</file>
@ -212,7 +213,9 @@
<file>./graphics/Numix/22/categories/applications-science.svg</file>
<file>./graphics/Numix/22/categories/applications-system.svg</file>
<file>./graphics/Numix/22/actions/arrow-down.svg</file>
<file>./graphics/Numix/22/actions/arrow-down-double.svg</file>
<file>./graphics/Numix/22/actions/arrow-up.svg</file>
<file>./graphics/Numix/22/actions/arrow-up-double.svg</file>
<file>./graphics/Numix/22/actions/call-start.svg</file>
<file>./graphics/Numix/22/status/dialog-error.svg</file>
<file>./graphics/Numix/22/status/dialog-information.svg</file>
@ -238,12 +241,11 @@
<file>./graphics/Numix/22/places/folder.svg</file>
<file>./graphics/Numix/22/actions/format-indent-more.svg</file>
<file>./graphics/Numix/22/actions/format-justify-fill.svg</file>
<file>./graphics/Numix/22/actions/go-down.svg</file>
<file>./graphics/Numix/22/actions/format-text-bold.svg</file>
<file>./graphics/Numix/22/actions/go-home.svg</file>
<file>./graphics/Numix/22/actions/go-jump.svg</file>
<file>./graphics/Numix/22/actions/go-next.svg</file>
<file>./graphics/Numix/22/actions/go-previous.svg</file>
<file>./graphics/Numix/22/actions/go-up.svg</file>
<file>./graphics/Numix/22/actions/gtk-edit.svg</file>
<file>./graphics/Numix/22/categories/help-about.svg</file>
<file>./graphics/Numix/22/actions/help-contents.svg</file>

View File

@ -5,6 +5,7 @@ CREATE TABLE Information (
-- !
CREATE TABLE Accounts (
id $$,
ordr INTEGER NOT NULL CHECK (ordr >= 0),
type TEXT NOT NULL CHECK (type != ''), /* ID of the account type. Each account defines its own, for example 'ttrss'. */
proxy_type INTEGER NOT NULL DEFAULT 0 CHECK (proxy_type >= 0),
proxy_host TEXT,
@ -17,6 +18,7 @@ CREATE TABLE Accounts (
-- !
CREATE TABLE Categories (
id $$,
ordr INTEGER NOT NULL CHECK (ordr >= 0),
parent_id INTEGER NOT NULL CHECK (parent_id >= -1), /* Root categories contain -1 here. */
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
@ -30,6 +32,7 @@ CREATE TABLE Categories (
-- !
CREATE TABLE Feeds (
id $$,
ordr INTEGER NOT NULL CHECK (ordr >= 0),
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created BIGINT,

View File

@ -1,3 +1,8 @@
USE ##;
-- !
SET FOREIGN_KEY_CHECKS = 0;
-- !
!! db_update_sqlite_1_2.sql
-- !
SET FOREIGN_KEY_CHECKS = 1;
-- !

View File

@ -1,9 +1,8 @@
CREATE TABLE backup_Feeds AS SELECT * FROM Feeds;
-- !
DROP TABLE Feeds;
ALTER TABLE Feeds RENAME TO backup_Feeds;
-- !
CREATE TABLE Feeds (
id $$,
ordr INTEGER NOT NULL CHECK (ordr >= 0),
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created BIGINT,
@ -22,8 +21,51 @@ CREATE TABLE Feeds (
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
);
-- !
INSERT INTO Feeds (id, title, description, date_created, icon, category, source, update_type, update_interval, account_id, custom_id, custom_data)
SELECT id, title, description, date_created, icon, category, source, update_type, update_interval, account_id, custom_id, custom_data
INSERT INTO Feeds (id, ordr, title, description, date_created, icon, category, source, update_type, update_interval, account_id, custom_id, custom_data)
SELECT id, id, title, description, date_created, icon, category, source, update_type, update_interval, account_id, custom_id, custom_data
FROM backup_Feeds;
-- !
DROP TABLE backup_Feeds;
-- !
ALTER TABLE Categories RENAME TO backup_Categories;
-- !
CREATE TABLE Categories (
id $$,
ordr INTEGER NOT NULL CHECK (ordr >= 0),
parent_id INTEGER NOT NULL CHECK (parent_id >= -1), /* Root categories contain -1 here. */
title TEXT NOT NULL CHECK (title != ''),
description TEXT,
date_created BIGINT,
icon ^^,
account_id INTEGER NOT NULL,
custom_id TEXT,
FOREIGN KEY (account_id) REFERENCES Accounts (id) ON DELETE CASCADE
);
-- !
INSERT INTO Categories (id, ordr, parent_id, title, description, date_created, icon, account_id, custom_id)
SELECT id, id, parent_id, title, description, date_created, icon, account_id, custom_id
FROM backup_Categories;
-- !
DROP TABLE backup_Categories;
-- !
ALTER TABLE Accounts RENAME TO backup_Accounts;
-- !
CREATE TABLE Accounts (
id $$,
ordr INTEGER NOT NULL CHECK (ordr >= 0),
type TEXT NOT NULL CHECK (type != ''), /* ID of the account type. Each account defines its own, for example 'ttrss'. */
proxy_type INTEGER NOT NULL DEFAULT 0 CHECK (proxy_type >= 0),
proxy_host TEXT,
proxy_port INTEGER,
proxy_username TEXT,
proxy_password TEXT,
/* Custom column for (serialized) custom account-specific data. */
custom_data TEXT
);
-- !
INSERT INTO Accounts (id, ordr, type, proxy_type, proxy_host, proxy_port, proxy_username, proxy_password, custom_data)
SELECT id, id, type, proxy_type, proxy_host, proxy_port, proxy_username, proxy_password, custom_data
FROM backup_Accounts;
-- !
DROP TABLE backup_Accounts;

View File

@ -13,7 +13,7 @@
FeedsProxyModel::FeedsProxyModel(FeedsModel* source_model, QObject* parent)
: QSortFilterProxyModel(parent), m_sourceModel(source_model), m_view(nullptr),
m_selectedItem(nullptr), m_showUnreadOnly(false) {
m_selectedItem(nullptr), m_showUnreadOnly(false), m_sortAlphabetically(true) {
setObjectName(QSL("FeedsProxyModel"));
setSortRole(Qt::ItemDataRole::EditRole);
@ -172,6 +172,7 @@ bool FeedsProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right
return sortOrder() == Qt::SortOrder::DescendingOrder;
}
else if (left_item->kind() == right_item->kind()) {
if (m_sortAlphabetically) {
// Both items are of the same type.
if (left.column() == FDS_MODEL_COUNTS_INDEX) {
// User wants to sort according to counts.
@ -182,6 +183,23 @@ bool FeedsProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right
return QString::localeAwareCompare(left_item->title().toLower(), right_item->title().toLower()) < 0;
}
}
else {
// We sort some types with sort order, other alphabetically.
switch (left_item->kind()) {
case RootItem::Kind::Feed:
case RootItem::Kind::Category:
case RootItem::Kind::ServiceRoot:
return sortOrder() == Qt::SortOrder::AscendingOrder
? left_item->sortOrder() < right_item->sortOrder()
: left_item->sortOrder() > right_item->sortOrder();
default:
return sortOrder() == Qt::SortOrder::AscendingOrder
? QString::localeAwareCompare(left_item->title().toLower(), right_item->title().toLower()) < 0
: QString::localeAwareCompare(left_item->title().toLower(), right_item->title().toLower()) > 0;
}
}
}
else {
// We sort using priorities.
auto left_priority = m_priorities.indexOf(left_item->kind());
@ -265,6 +283,10 @@ bool FeedsProxyModel::filterAcceptsRowInternal(int source_row, const QModelIndex
}
}
void FeedsProxyModel::sort(int column, Qt::SortOrder order) {
QSortFilterProxyModel::sort(column, order);
}
void FeedsProxyModel::setView(FeedsView* newView) {
m_view = newView;
}
@ -294,6 +316,14 @@ void FeedsProxyModel::setShowUnreadOnly(bool show_unread_only) {
qApp->settings()->setValue(GROUP(Feeds), Feeds::ShowOnlyUnreadFeeds, show_unread_only);
}
void FeedsProxyModel::setSortAlphabetically(bool sort_alphabetically) {
if (sort_alphabetically != m_sortAlphabetically) {
m_sortAlphabetically = sort_alphabetically;
qApp->settings()->setValue(GROUP(Feeds), Feeds::SortAlphabetically, sort_alphabetically);
invalidate();
}
}
QModelIndexList FeedsProxyModel::mapListToSource(const QModelIndexList& indexes) const {
QModelIndexList source_indexes;

View File

@ -17,9 +17,11 @@ class FeedsProxyModel : public QSortFilterProxyModel {
explicit FeedsProxyModel(FeedsModel* source_model, QObject* parent = nullptr);
virtual ~FeedsProxyModel();
virtual void sort(int column, Qt::SortOrder order = Qt::SortOrder::AscendingOrder);
// Returns index list of items which "match" given value.
// Used for finding items according to entered title text.
QModelIndexList match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const;
virtual QModelIndexList match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const;
// Maps list of indexes.
QModelIndexList mapListToSource(const QModelIndexList& indexes) const;
@ -32,24 +34,27 @@ class FeedsProxyModel : public QSortFilterProxyModel {
void setSelectedItem(const RootItem* selected_item);
void setView(FeedsView* newView);
void setSortAlphabetically(bool sort_alphabetically);
public slots:
void invalidateReadFeedsFilter(bool set_new_value = false, bool show_unread_only = false);
signals:
void expandAfterFilterIn(QModelIndex source_idx) const;
private:
protected:
virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const;
virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
// Compares two rows of data.
bool lessThan(const QModelIndex& left, const QModelIndex& right) const;
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
bool filterAcceptsRowInternal(int source_row, const QModelIndex& source_parent) const;
private:
virtual bool filterAcceptsRowInternal(int source_row, const QModelIndex& source_parent) const;
// Source model pointer.
FeedsModel* m_sourceModel;
FeedsView* m_view;
const RootItem* m_selectedItem;
bool m_showUnreadOnly;
bool m_sortAlphabetically;
QList<QPair<int, QModelIndex>> m_hiddenIndices;
QList<RootItem::Kind> m_priorities;
};

View File

@ -1940,8 +1940,8 @@ void DatabaseQueries::createOverwriteCategory(const QSqlDatabase& db, Category*
if (category->id() <= 0) {
// We need to insert category first.
q.prepare(QSL("INSERT INTO "
"Categories (parent_id, title, date_created, account_id) "
"VALUES (0, 'new', 0, %1);").arg(QString::number(account_id)));
"Categories (parent_id, ordr, title, date_created, account_id) "
"VALUES (0, 0, 'new', 0, %1);").arg(QString::number(account_id)));
if (!q.exec()) {
throw ApplicationException(q.lastError().text());
@ -1952,7 +1952,7 @@ void DatabaseQueries::createOverwriteCategory(const QSqlDatabase& db, Category*
}
q.prepare("UPDATE Categories "
"SET parent_id = :parent_id, title = :title, description = :description, date_created = :date_created, "
"SET parent_id = :parent_id, ordr = :ordr, title = :title, description = :description, date_created = :date_created, "
" icon = :icon, account_id = :account_id, custom_id = :custom_id "
"WHERE id = :id;");
q.bindValue(QSL(":parent_id"), parent_id);
@ -1963,6 +1963,7 @@ void DatabaseQueries::createOverwriteCategory(const QSqlDatabase& db, Category*
q.bindValue(QSL(":account_id"), account_id);
q.bindValue(QSL(":custom_id"), category->customId());
q.bindValue(QSL(":id"), category->id());
q.bindValue(QSL(":ordr"), category->sortOrder());
if (!q.exec()) {
throw ApplicationException(q.lastError().text());
@ -1975,8 +1976,8 @@ void DatabaseQueries::createOverwriteFeed(const QSqlDatabase& db, Feed* feed, in
if (feed->id() <= 0) {
// We need to insert feed first.
q.prepare(QSL("INSERT INTO "
"Feeds (title, date_created, category, update_type, update_interval, account_id, custom_id) "
"VALUES ('new', 0, 0, 0, 1, %1, 'new');").arg(QString::number(account_id)));
"Feeds (title, ordr, date_created, category, update_type, update_interval, account_id, custom_id) "
"VALUES ('new', 0, 0, 0, 0, 1, %1, 'new');").arg(QString::number(account_id)));
if (!q.exec()) {
throw ApplicationException(q.lastError().text());
@ -1991,7 +1992,7 @@ void DatabaseQueries::createOverwriteFeed(const QSqlDatabase& db, Feed* feed, in
}
q.prepare("UPDATE Feeds "
"SET title = :title, description = :description, date_created = :date_created, "
"SET title = :title, ordr = :ordr, description = :description, date_created = :date_created, "
" icon = :icon, category = :category, source = :source, update_type = :update_type, "
" update_interval = :update_interval, is_off = :is_off, open_articles = :open_articles, "
" account_id = :account_id, custom_id = :custom_id, custom_data = :custom_data "
@ -2007,6 +2008,7 @@ void DatabaseQueries::createOverwriteFeed(const QSqlDatabase& db, Feed* feed, in
q.bindValue(QSL(":account_id"), account_id);
q.bindValue(QSL(":custom_id"), feed->customId());
q.bindValue(QSL(":id"), feed->id());
q.bindValue(QSL(":ordr"), feed->sortOrder());
q.bindValue(QSL(":is_off"), feed->isSwitchedOff());
q.bindValue(QSL(":open_articles"), feed->openArticlesDirectly());
@ -2025,14 +2027,13 @@ void DatabaseQueries::createOverwriteAccount(const QSqlDatabase& db, ServiceRoot
if (account->accountId() <= 0) {
// We need to insert account first.
q.prepare(QSL("INSERT INTO Accounts (type) VALUES (:type);"));
q.prepare(QSL("INSERT INTO Accounts (ordr, type) VALUES (0, :type);"));
q.bindValue(QSL(":type"), account->code());
if (!q.exec()) {
throw ApplicationException(q.lastError().text());
}
else {
//account->setId(q.lastInsertId().toInt());
account->setAccountId(q.lastInsertId().toInt());
}
}
@ -2042,7 +2043,7 @@ void DatabaseQueries::createOverwriteAccount(const QSqlDatabase& db, ServiceRoot
q.prepare(QSL("UPDATE Accounts "
"SET proxy_type = :proxy_type, proxy_host = :proxy_host, proxy_port = :proxy_port, "
" proxy_username = :proxy_username, proxy_password = :proxy_password, "
" proxy_username = :proxy_username, proxy_password = :proxy_password, ordr = :ordr, "
" custom_data = :custom_data "
"WHERE id = :id"));
q.bindValue(QSL(":proxy_type"), proxy.type());
@ -2051,6 +2052,7 @@ void DatabaseQueries::createOverwriteAccount(const QSqlDatabase& db, ServiceRoot
q.bindValue(QSL(":proxy_username"), proxy.user());
q.bindValue(QSL(":proxy_password"), TextFactory::encrypt(proxy.password()));
q.bindValue(QSL(":id"), account->accountId());
q.bindValue(QSL(":ordr"), account->sortOrder());
auto custom_data = account->customDatabaseData();
QString serialized_custom_data = serializeCustomData(custom_data);
@ -2093,6 +2095,10 @@ bool DatabaseQueries::deleteCategory(const QSqlDatabase& db, int id) {
return q.exec();
}
void DatabaseQueries::moveItemUp(RootItem* item, const QSqlDatabase& db) {}
void DatabaseQueries::moveItemDown(RootItem* item, const QSqlDatabase& db) {}
MessageFilter* DatabaseQueries::addMessageFilter(const QSqlDatabase& db, const QString& title,
const QString& script) {
if (!db.driver()->hasFeature(QSqlDriver::DriverFeature::LastInsertId)) {

View File

@ -108,7 +108,7 @@ class DatabaseQueries {
static QList<ServiceRoot*> getAccounts(const QSqlDatabase& db, const QString& code, bool* ok = nullptr);
template<typename Categ, typename Fee>
static void loadFromDatabase(ServiceRoot* root);
static void loadRootFromDatabase(ServiceRoot* root);
static bool storeNewOauthTokens(const QSqlDatabase& db, const QString& refresh_token, int account_id);
static void createOverwriteAccount(const QSqlDatabase& db, ServiceRoot* account);
@ -134,6 +134,10 @@ class DatabaseQueries {
static Assignment getFeeds(const QSqlDatabase& db, const QList<MessageFilter*>& global_filters,
int account_id, bool* ok = nullptr);
// Item order methods.
static void moveItemUp(RootItem* item, const QSqlDatabase& db);
static void moveItemDown(RootItem* item, const QSqlDatabase& db);
// Message filters operators.
static bool purgeLeftoverMessageFilterAssignments(const QSqlDatabase& db, int account_id);
static MessageFilter* addMessageFilter(const QSqlDatabase& db, const QString& title, const QString& script);
@ -167,6 +171,7 @@ QList<ServiceRoot*> DatabaseQueries::getAccounts(const QSqlDatabase& db, const Q
// Load common data.
root->setAccountId(query.value(QSL("id")).toInt());
root->setSortOrder(query.value(QSL("ordr")).toInt());
QNetworkProxy proxy(QNetworkProxy::ProxyType(query.value(QSL("proxy_type")).toInt()),
query.value(QSL("proxy_host")).toString(),
@ -232,6 +237,7 @@ Assignment DatabaseQueries::getCategories(const QSqlDatabase& db, int account_id
auto* cat = static_cast<Category*>(pair.second);
cat->setId(query_categories.value(CAT_DB_ID_INDEX).toInt());
cat->setSortOrder(query_categories.value(CAT_DB_ORDER_INDEX).toInt());
cat->setCustomId(query_categories.value(CAT_DB_CUSTOM_ID_INDEX).toString());
if (cat->customId().isEmpty()) {
@ -287,6 +293,7 @@ Assignment DatabaseQueries::getFeeds(const QSqlDatabase& db,
// Load common data.
feed->setTitle(query.value(FDS_DB_TITLE_INDEX).toString());
feed->setId(query.value(FDS_DB_ID_INDEX).toInt());
feed->setSortOrder(query.value(FDS_DB_ORDER_INDEX).toInt());
feed->setSource(query.value(FDS_DB_SOURCE_INDEX).toString());
feed->setCustomId(query.value(FDS_DB_CUSTOM_ID_INDEX).toString());
@ -328,7 +335,7 @@ Assignment DatabaseQueries::getFeeds(const QSqlDatabase& db,
}
template<typename Categ, typename Fee>
void DatabaseQueries::loadFromDatabase(ServiceRoot* root) {
void DatabaseQueries::loadRootFromDatabase(ServiceRoot* root) {
QSqlDatabase database = qApp->database()->driver()->connection(root->metaObject()->className());
Assignment categories = DatabaseQueries::getCategories<Categ>(database, root->accountId());
Assignment feeds = DatabaseQueries::getFeeds<Fee>(database, qApp->feedReader()->messageFilters(), root->accountId());

View File

@ -119,6 +119,9 @@
#define CLI_NSTDOUTERR_SHORT "n"
#define CLI_NSTDOUTERR_LONG "no-standard-output"
#define CLI_STYLE_SHORT "t"
#define CLI_STYLE_LONG "style"
#define CLI_NDEBUG_SHORT "g"
#define CLI_NDEBUG_LONG "no-debug-output"
@ -240,29 +243,31 @@
// Indexes of columns as they are DEFINED IN THE TABLE for CATEGORIES.
#define CAT_DB_ID_INDEX 0
#define CAT_DB_PARENT_ID_INDEX 1
#define CAT_DB_TITLE_INDEX 2
#define CAT_DB_DESCRIPTION_INDEX 3
#define CAT_DB_DCREATED_INDEX 4
#define CAT_DB_ICON_INDEX 5
#define CAT_DB_ACCOUNT_ID_INDEX 6
#define CAT_DB_CUSTOM_ID_INDEX 7
#define CAT_DB_ORDER_INDEX 1
#define CAT_DB_PARENT_ID_INDEX 2
#define CAT_DB_TITLE_INDEX 3
#define CAT_DB_DESCRIPTION_INDEX 4
#define CAT_DB_DCREATED_INDEX 5
#define CAT_DB_ICON_INDEX 6
#define CAT_DB_ACCOUNT_ID_INDEX 7
#define CAT_DB_CUSTOM_ID_INDEX 8
// Indexes of columns as they are DEFINED IN THE TABLE for FEEDS.
#define FDS_DB_ID_INDEX 0
#define FDS_DB_TITLE_INDEX 1
#define FDS_DB_DESCRIPTION_INDEX 2
#define FDS_DB_DCREATED_INDEX 3
#define FDS_DB_ICON_INDEX 4
#define FDS_DB_CATEGORY_INDEX 5
#define FDS_DB_SOURCE_INDEX 6
#define FDS_DB_UPDATE_TYPE_INDEX 7
#define FDS_DB_UPDATE_INTERVAL_INDEX 8
#define FDS_DB_IS_OFF_INDEX 9
#define FDS_DB_OPEN_ARTICLES_INDEX 10
#define FDS_DB_ACCOUNT_ID_INDEX 11
#define FDS_DB_CUSTOM_ID_INDEX 12
#define FDS_DB_CUSTOM_DATA_INDEX 13
#define FDS_DB_ORDER_INDEX 1
#define FDS_DB_TITLE_INDEX 2
#define FDS_DB_DESCRIPTION_INDEX 3
#define FDS_DB_DCREATED_INDEX 4
#define FDS_DB_ICON_INDEX 5
#define FDS_DB_CATEGORY_INDEX 6
#define FDS_DB_SOURCE_INDEX 7
#define FDS_DB_UPDATE_TYPE_INDEX 8
#define FDS_DB_UPDATE_INTERVAL_INDEX 9
#define FDS_DB_IS_OFF_INDEX 10
#define FDS_DB_OPEN_ARTICLES_INDEX 11
#define FDS_DB_ACCOUNT_ID_INDEX 12
#define FDS_DB_CUSTOM_ID_INDEX 13
#define FDS_DB_CUSTOM_DATA_INDEX 14
// Indexes of columns for feed models.
#define FDS_MODEL_TITLE_INDEX 0

View File

@ -23,6 +23,7 @@ FormAddEditLabel::FormAddEditLabel(QWidget* parent) : QDialog(parent), m_editabl
});
m_ui.m_txtName->lineEdit()->setText(tr("Hot stuff"));
m_ui.m_txtName->lineEdit()->setFocus();
}
Label* FormAddEditLabel::execForAdd() {

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>224</width>
<width>270</width>
<height>97</height>
</rect>
</property>

View File

@ -56,7 +56,7 @@
FormMain::FormMain(QWidget* parent, Qt::WindowFlags f)
: QMainWindow(parent, f), m_ui(new Ui::FormMain), m_trayMenu(nullptr), m_statusBar(nullptr) {
qDebugNN << LOGSEC_GUI << "Creating main application form in thread: '" << QThread::currentThreadId() << "'.";
qDebugNN << LOGSEC_GUI << "Creating main application form in thread:" << QUOTE_W_SPACE_DOT(QThread::currentThreadId());
m_ui->setupUi(this);
qApp->setMainForm(this);
@ -196,6 +196,7 @@ QList<QAction*> FormMain::allActions() const {
actions << m_ui->m_actionClearSelectedItems;
actions << m_ui->m_actionClearAllItems;
actions << m_ui->m_actionShowOnlyUnreadItems;
actions << m_ui->m_actionSortFeedsAlphabetically;
actions << m_ui->m_actionShowTreeBranches;
actions << m_ui->m_actionAutoExpandItemsWhenSelected;
actions << m_ui->m_actionShowOnlyUnreadMessages;
@ -216,6 +217,10 @@ QList<QAction*> FormMain::allActions() const {
actions << m_ui->m_actionServiceDelete;
actions << m_ui->m_actionCleanupDatabase;
actions << m_ui->m_actionAddFeedIntoSelectedItem;
actions << m_ui->m_actionFeedMoveUp;
actions << m_ui->m_actionFeedMoveDown;
actions << m_ui->m_actionFeedMoveTop;
actions << m_ui->m_actionFeedMoveBottom;
actions << m_ui->m_actionAddCategoryIntoSelectedItem;
actions << m_ui->m_actionViewSelectedItemsNewspaperMode;
actions << m_ui->m_actionSelectNextItem;
@ -475,6 +480,7 @@ void FormMain::updateFeedButtonsAvailability() {
const bool feed_selected = anything_selected && selected_item->kind() == RootItem::Kind::Feed;
const bool category_selected = anything_selected && selected_item->kind() == RootItem::Kind::Category;
const bool service_selected = anything_selected && selected_item->kind() == RootItem::Kind::ServiceRoot;
const bool manual_feed_sort = !m_ui->m_actionSortFeedsAlphabetically->isChecked();
m_ui->m_actionStopRunningItemsUpdate->setEnabled(is_update_running);
m_ui->m_actionBackupDatabaseSettings->setEnabled(!critical_action_running);
@ -498,6 +504,11 @@ void FormMain::updateFeedButtonsAvailability() {
m_ui->m_menuAddItem->setEnabled(!critical_action_running);
m_ui->m_menuAccounts->setEnabled(!critical_action_running);
m_ui->m_menuRecycleBin->setEnabled(!critical_action_running);
m_ui->m_actionFeedMoveUp->setEnabled(manual_feed_sort &&(feed_selected || category_selected || service_selected));
m_ui->m_actionFeedMoveDown->setEnabled(manual_feed_sort &&(feed_selected || category_selected || service_selected));
m_ui->m_actionFeedMoveTop->setEnabled(manual_feed_sort &&(feed_selected || category_selected || service_selected));
m_ui->m_actionFeedMoveBottom->setEnabled(manual_feed_sort &&(feed_selected || category_selected || service_selected));
}
void FormMain::switchVisibility(bool force_hide) {
@ -590,11 +601,12 @@ void FormMain::setupIcons() {
m_ui->m_actionOpenSelectedMessagesInternallyNoTab->setIcon(icon_theme_factory->fromTheme(QSL("document-open")));
m_ui->m_actionSendMessageViaEmail->setIcon(icon_theme_factory->fromTheme(QSL("mail-send")));
m_ui->m_actionViewSelectedItemsNewspaperMode->setIcon(icon_theme_factory->fromTheme(QSL("format-justify-fill")));
m_ui->m_actionSelectNextItem->setIcon(icon_theme_factory->fromTheme(QSL("go-down")));
m_ui->m_actionSelectPreviousItem->setIcon(icon_theme_factory->fromTheme(QSL("go-up")));
m_ui->m_actionSelectNextMessage->setIcon(icon_theme_factory->fromTheme(QSL("go-down")));
m_ui->m_actionSelectPreviousMessage->setIcon(icon_theme_factory->fromTheme(QSL("go-up")));
m_ui->m_actionSelectNextItem->setIcon(icon_theme_factory->fromTheme(QSL("arrow-down")));
m_ui->m_actionSelectPreviousItem->setIcon(icon_theme_factory->fromTheme(QSL("arrow-up")));
m_ui->m_actionSelectNextMessage->setIcon(icon_theme_factory->fromTheme(QSL("arrow-down")));
m_ui->m_actionSelectPreviousMessage->setIcon(icon_theme_factory->fromTheme(QSL("arrow-up")));
m_ui->m_actionSelectNextUnreadMessage->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread")));
m_ui->m_actionSortFeedsAlphabetically->setIcon(icon_theme_factory->fromTheme(QSL("format-text-bold")));
m_ui->m_actionShowOnlyUnreadItems->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread")));
m_ui->m_actionShowOnlyUnreadMessages->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread")));
m_ui->m_actionExpandCollapseItem->setIcon(icon_theme_factory->fromTheme(QSL("format-indent-more")));
@ -609,6 +621,11 @@ void FormMain::setupIcons() {
m_ui->m_actionAddCategoryIntoSelectedItem->setIcon(icon_theme_factory->fromTheme(QSL("folder")));
m_ui->m_actionMessageFilters->setIcon(icon_theme_factory->fromTheme(QSL("view-list-details")));
m_ui->m_actionFeedMoveUp->setIcon(icon_theme_factory->fromTheme(QSL("arrow-up")));
m_ui->m_actionFeedMoveDown->setIcon(icon_theme_factory->fromTheme(QSL("arrow-down")));
m_ui->m_actionFeedMoveTop->setIcon(icon_theme_factory->fromTheme(QSL("arrow-up-double")));
m_ui->m_actionFeedMoveBottom->setIcon(icon_theme_factory->fromTheme(QSL("arrow-down-double")));
// Tabs & web browser.
m_ui->m_actionTabNewWebBrowser->setIcon(icon_theme_factory->fromTheme(QSL("tab-new")));
m_ui->m_actionTabsCloseAll->setIcon(icon_theme_factory->fromTheme(QSL("window-close")));
@ -667,6 +684,8 @@ void FormMain::loadSize() {
m_ui->m_actionSwitchStatusBar->setChecked(settings->value(GROUP(GUI), SETTING(GUI::StatusBarVisible)).toBool());
// Other startup GUI-related settings.
m_ui->m_actionSortFeedsAlphabetically->setChecked(settings->value(GROUP(Feeds),
SETTING(Feeds::SortAlphabetically)).toBool());
m_ui->m_actionShowOnlyUnreadItems->setChecked(settings->value(GROUP(Feeds),
SETTING(Feeds::ShowOnlyUnreadFeeds)).toBool());
m_ui->m_actionShowTreeBranches->setChecked(settings->value(GROUP(Feeds),
@ -858,6 +877,8 @@ void FormMain::createConnections() {
tabWidget()->feedMessageViewer(), &FeedMessageViewer::switchMessageSplitterOrientation);
connect(m_ui->m_actionShowOnlyUnreadItems, &QAction::toggled,
tabWidget()->feedMessageViewer(), &FeedMessageViewer::toggleShowOnlyUnreadFeeds);
connect(m_ui->m_actionSortFeedsAlphabetically, &QAction::toggled,
tabWidget()->feedMessageViewer()->feedsView(), &FeedsView::toggleFeedSortingMode);
connect(m_ui->m_actionShowTreeBranches, &QAction::toggled,
tabWidget()->feedMessageViewer(), &FeedMessageViewer::toggleShowFeedTreeBranches);
connect(m_ui->m_actionAutoExpandItemsWhenSelected, &QAction::toggled,

View File

@ -107,6 +107,16 @@
<string>&amp;Add item</string>
</property>
</widget>
<widget class="QMenu" name="m_menuFeedsMove">
<property name="title">
<string>&amp;Move</string>
</property>
<addaction name="m_actionFeedMoveUp"/>
<addaction name="m_actionFeedMoveDown"/>
<addaction name="separator"/>
<addaction name="m_actionFeedMoveTop"/>
<addaction name="m_actionFeedMoveBottom"/>
</widget>
<addaction name="m_actionUpdateAllItems"/>
<addaction name="m_actionUpdateSelectedItems"/>
<addaction name="m_actionUpdateSelectedItemsWithCustomTimers"/>
@ -116,11 +126,13 @@
<addaction name="m_actionEditSelectedItem"/>
<addaction name="m_actionDeleteSelectedItem"/>
<addaction name="separator"/>
<addaction name="m_actionSortFeedsAlphabetically"/>
<addaction name="m_actionShowOnlyUnreadItems"/>
<addaction name="m_actionAutoExpandItemsWhenSelected"/>
<addaction name="m_actionShowTreeBranches"/>
<addaction name="m_actionExpandCollapseItem"/>
<addaction name="m_actionExpandCollapseItemRecursively"/>
<addaction name="m_menuFeedsMove"/>
<addaction name="separator"/>
<addaction name="m_actionSelectNextItem"/>
<addaction name="m_actionSelectPreviousItem"/>
@ -853,6 +865,37 @@
<string>Open in internal browser (no new tab)</string>
</property>
</action>
<action name="m_actionSortFeedsAlphabetically">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Sort alphabetically</string>
</property>
</action>
<action name="m_actionFeedMoveUp">
<property name="text">
<string>Move &amp;up</string>
</property>
</action>
<action name="m_actionFeedMoveTop">
<property name="text">
<string>Move to &amp;top</string>
</property>
</action>
<action name="m_actionFeedMoveDown">
<property name="text">
<string>Move &amp;down</string>
</property>
</action>
<action name="m_actionFeedMoveBottom">
<property name="text">
<string>Move to &amp;bottom</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -512,6 +512,11 @@ void FeedsView::filterItems(const QString& pattern) {
}
}
void FeedsView::toggleFeedSortingMode(bool sort_alphabetically) {
setSortingEnabled(sort_alphabetically);
m_proxyModel->setSortAlphabetically(sort_alphabetically);
}
void FeedsView::onIndexExpanded(const QModelIndex& idx) {
qDebugNN << LOGSEC_GUI << "Feed list item expanded - " << m_proxyModel->data(idx).toString();
@ -698,6 +703,15 @@ QMenu* FeedsView::initializeContextMenuFeeds(RootItem* clicked_item) {
m_contextMenuFeeds->addAction(qApp->mainForm()->m_ui->m_actionAddFeedIntoSelectedItem);
}
if (!qApp->settings()->value(GROUP(Feeds),
SETTING(Feeds::SortAlphabetically)).toBool()) {
m_contextMenuFeeds->addSeparator();
m_contextMenuFeeds->addAction(qApp->mainForm()->m_ui->m_actionFeedMoveUp);
m_contextMenuFeeds->addAction(qApp->mainForm()->m_ui->m_actionFeedMoveDown);
m_contextMenuFeeds->addAction(qApp->mainForm()->m_ui->m_actionFeedMoveTop);
m_contextMenuFeeds->addAction(qApp->mainForm()->m_ui->m_actionFeedMoveBottom);
}
if (!specific_actions.isEmpty()) {
m_contextMenuFeeds->addSeparator();
m_contextMenuFeeds->addActions(specific_actions);
@ -792,6 +806,7 @@ void FeedsView::setupAppearance() {
setUniformRowHeights(true);
setAnimated(true);
setSortingEnabled(true);
setItemsExpandable(true);
setAutoExpandDelay(0);

View File

@ -76,7 +76,7 @@ class RSSGUARD_DLLSPEC FeedsView : public BaseTreeView {
void switchVisibility();
void filterItems(const QString& pattern);
void toggleFeedSortingMode(bool sort_alphabetically);
void invalidateReadFeedsFilter(bool set_new_value = false, bool show_unread_only = false);
signals:

View File

@ -28,11 +28,7 @@ void GuiUtilities::setLabelAsNotice(QLabel& label, bool is_warning, bool set_mar
void GuiUtilities::applyDialogProperties(QWidget& widget, const QIcon& icon, const QString& title) {
widget.setWindowFlags(
#if defined(Q_OS_LINUX)
Qt::WindowType::Window |
#else
Qt::WindowType::Dialog |
#endif
Qt::WindowType::WindowTitleHint |
Qt::WindowType::WindowMaximizeButtonHint |
Qt::WindowType::WindowCloseButtonHint);

View File

@ -11,7 +11,7 @@
#include <QStyle>
TabBar::TabBar(QWidget* parent) : QTabBar(parent) {
setDocumentMode(false);
setDocumentMode(true);
setUsesScrollButtons(true);
setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
}

View File

@ -60,9 +60,9 @@
#endif
#endif
Application::Application(const QString& id, int& argc, char** argv)
Application::Application(const QString& id, int& argc, char** argv, const QStringList& raw_cli_args)
: SingleApplication(id, argc, argv), m_updateFeedsLock(new Mutex()) {
parseCmdArgumentsFromMyInstance();
parseCmdArgumentsFromMyInstance(raw_cli_args);
qInstallMessageHandler(performLogging);
m_feedReader = nullptr;
@ -941,7 +941,7 @@ void Application::parseCmdArgumentsFromOtherInstance(const QString& message) {
}
}
void Application::parseCmdArgumentsFromMyInstance() {
void Application::parseCmdArgumentsFromMyInstance(const QStringList& raw_cli_args) {
QCommandLineOption help({ QSL(CLI_HELP_SHORT), QSL(CLI_HELP_LONG) },
QSL("Displays overview of CLI."));
QCommandLineOption version({ QSL(CLI_VER_SHORT), QSL(CLI_VER_LONG) },
@ -958,15 +958,20 @@ void Application::parseCmdArgumentsFromMyInstance() {
QSL("Disable just \"debug\" output."));
QCommandLineOption disable_debug({ QSL(CLI_NSTDOUTERR_SHORT), QSL(CLI_NSTDOUTERR_LONG) },
QSL("Completely disable stdout/stderr outputs."));
QCommandLineOption forced_style({ QSL(CLI_STYLE_SHORT), QSL(CLI_STYLE_LONG) },
QSL("Force some application style."),
QSL("style-name"));
m_cmdParser.addOptions({ help, version, log_file, custom_data_folder,
disable_singleinstance, disable_only_debug, disable_debug });
disable_singleinstance, disable_only_debug, disable_debug,
forced_style });
m_cmdParser.addPositionalArgument(QSL("urls"),
QSL("List of URL addresses pointing to individual online feeds which should be added."),
QSL("[url-1 ... url-n]"));
m_cmdParser.setApplicationDescription(QSL(APP_NAME));
m_cmdParser.setSingleDashWordOptionMode(QCommandLineParser::SingleDashWordOptionMode::ParseAsLongOptions);
if (!m_cmdParser.parse(QCoreApplication::arguments())) {
if (!m_cmdParser.parse(raw_cli_args)) {
qCriticalNN << LOGSEC_CORE << m_cmdParser.errorText();
}

View File

@ -81,7 +81,7 @@ class RSSGUARD_DLLSPEC Application : public SingleApplication {
Q_OBJECT
public:
explicit Application(const QString& id, int& argc, char** argv);
explicit Application(const QString& id, int& argc, char** argv, const QStringList &raw_cli_args);
virtual ~Application();
void reactOnForeignNotifications();
@ -173,7 +173,7 @@ class RSSGUARD_DLLSPEC Application : public SingleApplication {
// Processes incoming message from another RSS Guard instance.
void parseCmdArgumentsFromOtherInstance(const QString& message);
void parseCmdArgumentsFromMyInstance();
void parseCmdArgumentsFromMyInstance(const QStringList &raw_cli_args);
private slots:
void onNodeJsPackageUpdateError(const QList<NodeJs::PackageMetadata>& pkgs, const QString& error);

View File

@ -88,6 +88,9 @@ DVALUE(double) Feeds::FeedsUpdateStartupDelayDef = STARTUP_UPDATE_DELAY;
DKEY Feeds::ShowOnlyUnreadFeeds = "show_only_unread_feeds";
DVALUE(bool) Feeds::ShowOnlyUnreadFeedsDef = false;
DKEY Feeds::SortAlphabetically = "sort_alphabetically";
DVALUE(bool) Feeds::SortAlphabeticallyDef = true;
DKEY Feeds::ShowTreeBranches = "show_tree_branches";
DVALUE(bool) Feeds::ShowTreeBranchesDef = true;

View File

@ -93,6 +93,9 @@ namespace Feeds {
KEY ShowOnlyUnreadFeeds;
VALUE(bool) ShowOnlyUnreadFeedsDef;
KEY SortAlphabetically;
VALUE(bool) SortAlphabeticallyDef;
KEY ShowTreeBranches;
VALUE(bool) ShowTreeBranchesDef;

View File

@ -51,9 +51,10 @@ bool SkinFactory::isStyleGoodForDarkVariant(const QString& style_name) const {
void SkinFactory::loadSkinFromData(const Skin& skin) {
QString style_name = qApp->settings()->value(GROUP(GUI), SETTING(GUI::Style)).toString();
auto env = QProcessEnvironment::systemEnvironment();
QString over_style = env.value(QSL("QT_STYLE_OVERRIDE"));
const QString env_forced_style = env.value(QSL("QT_STYLE_OVERRIDE"));
const QString cli_forced_style = qApp->cmdParser()->value(QSL(CLI_STYLE_SHORT));
if (over_style.isEmpty()) {
if (env_forced_style.isEmpty() && cli_forced_style.isEmpty()) {
qApp->setStyle(style_name);
m_styleIsFrozen = false;
@ -61,7 +62,9 @@ void SkinFactory::loadSkinFromData(const Skin& skin) {
}
else {
m_styleIsFrozen = true;
qWarningNN << LOGSEC_GUI << "Respecting forced style:" << QUOTE_W_SPACE_DOT(over_style);
qWarningNN << LOGSEC_GUI << "Respecting forced style(s):\n"
<< " QT_STYLE_OVERRIDE: " QUOTE_NO_SPACE(env_forced_style) << "\n"
<< " CLI (-style): " QUOTE_NO_SPACE(cli_forced_style);
}
if (isStyleGoodForDarkVariant(style_name) &&

View File

@ -91,7 +91,7 @@ class RSSGUARD_DLLSPEC SkinFactory : public QObject {
private:
// Loads the skin from give skin_data.
// Loads the skin from given skin_data.
void loadSkinFromData(const Skin& skin);
QString loadSkinFile(const QString& skin_folder, const QString& file_name, const QString& base_folder) const;

View File

@ -16,7 +16,7 @@
RootItem::RootItem(RootItem* parent_item)
: QObject(nullptr), m_kind(RootItem::Kind::Root), m_id(NO_PARENT_CATEGORY), m_customId(QL1S("")),
m_title(QString()), m_description(QString()), m_creationDate(QDateTime::currentDateTimeUtc()),
m_keepOnTop(false), m_childItems(QList<RootItem*>()), m_parentItem(parent_item) {}
m_keepOnTop(false), m_sortOrder(0), m_childItems(QList<RootItem*>()), m_parentItem(parent_item) {}
RootItem::RootItem(const RootItem& other) : RootItem(nullptr) {
setTitle(other.title());
@ -24,6 +24,7 @@ RootItem::RootItem(const RootItem& other) : RootItem(nullptr) {
setCustomId(other.customId());
setIcon(other.icon());
setKeepOnTop(other.keepOnTop());
setSortOrder(other.sortOrder());
// NOTE: We do not need to clone childs, because that would mean that
// either source or target item tree would get corrupted.
@ -53,7 +54,7 @@ QString RootItem::additionalTooltip() const {
}
QList<QAction*> RootItem::contextMenuFeedsList() {
return QList<QAction*>();
return {};
}
bool RootItem::canBeEdited() const {
@ -564,6 +565,14 @@ void RootItem::setKeepOnTop(bool keep_on_top) {
m_keepOnTop = keep_on_top;
}
int RootItem::sortOrder() const {
return m_sortOrder;
}
void RootItem::setSortOrder(int sort_order) {
m_sortOrder = sort_order;
}
bool RootItem::removeChild(int index) {
if (index >= 0 && index < m_childItems.size()) {
m_childItems.removeAt(index);

View File

@ -90,12 +90,16 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
virtual QList<Message> undeletedMessages() const;
// This method should "clean" all messages it contains.
// What "clean" means? It means delete messages -> move them to recycle bin
//
// NOTE: What "clean" means? It means delete messages -> move them to recycle bin
// or eventually remove them completely if there is no recycle bin functionality.
//
// If this method is called on "recycle bin" instance of your
// service account, it should "empty" the recycle bin.
virtual bool cleanMessages(bool clear_only_read);
// Reloads current counts of articles in this item from DB and
// sets.
virtual void updateCounts(bool including_total_count);
virtual int row() const;
virtual QVariant data(int column, int role) const;
@ -195,6 +199,16 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
bool keepOnTop() const;
void setKeepOnTop(bool keep_on_top);
// Sort order, when items in feeds list are sorted manually.
//
// NOTE: This is only used for "Account", "Category" and "Feed" classes
// which can be manually sorted. Other types like "Label" cannot be
// automatically sorted and are always sorted by title.
//
// Sort order number cannot be negative.
int sortOrder() const;
void setSortOrder(int sort_order);
private:
RootItem::Kind m_kind;
int m_id;
@ -204,6 +218,7 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
QIcon m_icon;
QDateTime m_creationDate;
bool m_keepOnTop;
int m_sortOrder;
QList<RootItem*> m_childItems;
RootItem* m_parentItem;
};

View File

@ -77,7 +77,18 @@ bool ServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const {
}
QList<QAction*> ServiceRoot::contextMenuFeedsList() {
return serviceMenu();
auto specific = serviceMenu();
auto base = RootItem::contextMenuFeedsList();
if (!specific.isEmpty()) {
auto* act_sep = new QAction(this);
act_sep->setSeparator(true);
base.append(act_sep);
base.append(specific);
}
return base;
}
QList<QAction*> ServiceRoot::contextMenuMessagesList(const QList<Message>& messages) {

View File

@ -88,7 +88,7 @@ QList<Message> FeedlyServiceRoot::obtainNewMessages(Feed* feed,
void FeedlyServiceRoot::start(bool freshly_activated) {
if (!freshly_activated) {
DatabaseQueries::loadFromDatabase<Category, Feed>(this);
DatabaseQueries::loadRootFromDatabase<Category, Feed>(this);
loadCacheFromFile();
}

View File

@ -163,7 +163,7 @@ bool GmailServiceRoot::supportsCategoryAdding() const {
void GmailServiceRoot::start(bool freshly_activated) {
if (!freshly_activated) {
DatabaseQueries::loadFromDatabase<Category, Feed>(this);
DatabaseQueries::loadRootFromDatabase<Category, Feed>(this);
loadCacheFromFile();
}

View File

@ -1012,7 +1012,7 @@ QList<Message> GreaderNetwork::decodeStreamContents(ServiceRoot* root,
message.m_title = qApp->web()->unescapeHtml(message_obj[QSL("title")].toString());
message.m_author = qApp->web()->unescapeHtml(message_obj[QSL("author")].toString());
message.m_created = QDateTime::fromSecsSinceEpoch(message_obj[QSL("published")].toInt(), Qt::UTC);
message.m_created = QDateTime::fromSecsSinceEpoch(message_obj[QSL("published")].toInt(), Qt::TimeSpec::UTC);
message.m_createdFromFeed = true;
message.m_customId = message_obj[QSL("id")].toString();

View File

@ -155,7 +155,7 @@ bool GreaderServiceRoot::wantsBaggedIdsOfExistingMessages() const {
void GreaderServiceRoot::start(bool freshly_activated) {
if (!freshly_activated) {
DatabaseQueries::loadFromDatabase<Category, Feed>(this);
DatabaseQueries::loadRootFromDatabase<Category, Feed>(this);
loadCacheFromFile();
}

View File

@ -72,7 +72,7 @@ QList<Message> NewsBlurServiceRoot::obtainNewMessages(Feed* feed,
void NewsBlurServiceRoot::start(bool freshly_activated) {
if (!freshly_activated) {
DatabaseQueries::loadFromDatabase<Category, Feed>(this);
DatabaseQueries::loadRootFromDatabase<Category, Feed>(this);
loadCacheFromFile();
}

View File

@ -50,7 +50,7 @@ bool OwnCloudServiceRoot::supportsCategoryAdding() const {
void OwnCloudServiceRoot::start(bool freshly_activated) {
if (!freshly_activated) {
DatabaseQueries::loadFromDatabase<Category, OwnCloudFeed>(this);
DatabaseQueries::loadRootFromDatabase<Category, OwnCloudFeed>(this);
loadCacheFromFile();
}

View File

@ -94,7 +94,7 @@ bool RedditServiceRoot::supportsCategoryAdding() const {
void RedditServiceRoot::start(bool freshly_activated) {
if (!freshly_activated) {
DatabaseQueries::loadFromDatabase<RedditCategory, Feed>(this);
DatabaseQueries::loadRootFromDatabase<RedditCategory, Feed>(this);
loadCacheFromFile();
}

View File

@ -83,7 +83,7 @@ QString FeedParser::jsonMessageRawContents(const QJsonObject& msg_element) const
QList<Message> FeedParser::messages() {
QString feed_author = feedAuthor();
QList<Message> messages;
QDateTime current_time = QDateTime::currentDateTime();
QDateTime current_time = QDateTime::currentDateTimeUtc();
// Pull out all messages.
if (m_isXml) {

View File

@ -50,7 +50,7 @@ StandardServiceRoot::~StandardServiceRoot() {
}
void StandardServiceRoot::start(bool freshly_activated) {
DatabaseQueries::loadFromDatabase<StandardCategory, StandardFeed>(this);
DatabaseQueries::loadRootFromDatabase<StandardCategory, StandardFeed>(this);
if (freshly_activated && getSubTreeFeeds().isEmpty()) {
// In other words, if there are no feeds or categories added.

View File

@ -41,7 +41,7 @@ ServiceRoot::LabelOperation TtRssServiceRoot::supportedLabelOperations() const {
void TtRssServiceRoot::start(bool freshly_activated) {
if (!freshly_activated) {
DatabaseQueries::loadFromDatabase<Category, TtRssFeed>(this);
DatabaseQueries::loadRootFromDatabase<Category, TtRssFeed>(this);
loadCacheFromFile();
auto lbls = m_labelsNode->labels();

View File

@ -52,8 +52,17 @@ int main(int argc, char* argv[]) {
disableWindowTabbing();
#endif
// We create our own "arguments" list as Qt strips something
// sometimes out.
char** const av = argv;
QStringList raw_cli_args;
for (int a = 0; a < argc; a++) {
raw_cli_args << QString::fromLocal8Bit(av[a]);
}
// Instantiate base application object.
Application application(QSL(APP_LOW_NAME), argc, argv);
Application application(QSL(APP_LOW_NAME), argc, argv, raw_cli_args);
qDebugNN << LOGSEC_CORE << "Starting" << NONQUOTE_W_SPACE_DOT(APP_LONG_NAME);
qDebugNN << LOGSEC_CORE << "Instantiated class " << QUOTE_W_SPACE_DOT(application.metaObject()->className());
@ -97,8 +106,10 @@ int main(int argc, char* argv[]) {
qApp->showTrayIcon();
qApp->offerChanges();
qApp->showPolls();
qApp->mainForm()->tabWidget()->feedMessageViewer()->respondToMainWindowResizes();
qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->loadAllExpandStates();
main_window.tabWidget()->feedMessageViewer()->respondToMainWindowResizes();
main_window.tabWidget()->feedMessageViewer()->feedsView()->loadAllExpandStates();
qApp->parseCmdArgumentsFromOtherInstance(qApp->cmdParser()->positionalArguments().join(QSL(ARGUMENTS_LIST_SEPARATOR)));
return Application::exec();