From 7f5d1473a3b28e3baa99b51523b1e12eac1e177c Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 1 Mar 2022 14:45:20 +0100 Subject: [PATCH] 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. --- .../desktop/com.github.rssguard.appdata.xml | 2 +- resources/icons.qrc | 18 ++++--- resources/sql/db_init_sqlite.sql | 3 ++ resources/sql/db_update_mysql_1_2.sql | 7 ++- resources/sql/db_update_sqlite_1_2.sql | 54 ++++++++++++++++--- src/librssguard/core/feedsproxymodel.cpp | 44 ++++++++++++--- src/librssguard/core/feedsproxymodel.h | 17 +++--- src/librssguard/database/databasequeries.cpp | 24 +++++---- src/librssguard/database/databasequeries.h | 11 +++- src/librssguard/definitions/definitions.h | 45 +++++++++------- .../gui/dialogs/formaddeditlabel.cpp | 1 + .../gui/dialogs/formaddeditlabel.ui | 2 +- src/librssguard/gui/dialogs/formmain.cpp | 31 +++++++++-- src/librssguard/gui/dialogs/formmain.ui | 43 +++++++++++++++ src/librssguard/gui/feedsview.cpp | 27 +++++++--- src/librssguard/gui/feedsview.h | 2 +- src/librssguard/gui/guiutilities.cpp | 4 -- src/librssguard/gui/tabbar.cpp | 2 +- src/librssguard/miscellaneous/application.cpp | 15 ++++-- src/librssguard/miscellaneous/application.h | 4 +- src/librssguard/miscellaneous/settings.cpp | 3 ++ src/librssguard/miscellaneous/settings.h | 3 ++ src/librssguard/miscellaneous/skinfactory.cpp | 9 ++-- src/librssguard/miscellaneous/skinfactory.h | 4 +- .../services/abstract/rootitem.cpp | 13 ++++- src/librssguard/services/abstract/rootitem.h | 17 +++++- .../services/abstract/serviceroot.cpp | 13 ++++- .../services/feedly/feedlyserviceroot.cpp | 2 +- .../services/gmail/gmailserviceroot.cpp | 2 +- .../services/greader/greadernetwork.cpp | 2 +- .../services/greader/greaderserviceroot.cpp | 2 +- .../services/newsblur/newsblurserviceroot.cpp | 2 +- .../services/owncloud/owncloudserviceroot.cpp | 2 +- .../services/reddit/redditserviceroot.cpp | 2 +- .../services/standard/parsers/feedparser.cpp | 2 +- .../services/standard/standardserviceroot.cpp | 2 +- .../services/tt-rss/ttrssserviceroot.cpp | 2 +- src/rssguard/main.cpp | 17 ++++-- 38 files changed, 348 insertions(+), 107 deletions(-) diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index 281dbfc6b..432205d35 100644 --- a/resources/desktop/com.github.rssguard.appdata.xml +++ b/resources/desktop/com.github.rssguard.appdata.xml @@ -26,7 +26,7 @@ https://github.com/sponsors/martinrotter - + none diff --git a/resources/icons.qrc b/resources/icons.qrc index 2c3d443cb..814381b3e 100644 --- a/resources/icons.qrc +++ b/resources/icons.qrc @@ -7,7 +7,9 @@ ./graphics/Breeze/categories/32/applications-science.svg ./graphics/Breeze/categories/32/applications-system.svg ./graphics/Breeze/actions/32/arrow-down.svg + ./graphics/Breeze/actions/32/arrow-down-double.svg ./graphics/Breeze/actions/32/arrow-up.svg + ./graphics/Breeze/actions/32/arrow-up-double.svg ./graphics/Breeze/actions/32/call-start.svg ./graphics/Breeze/status/64/dialog-error.svg ./graphics/Breeze/status/64/dialog-information.svg @@ -30,12 +32,11 @@ ./graphics/Breeze/places/96/folder.svg ./graphics/Breeze/actions/22/format-indent-more.svg ./graphics/Breeze/actions/22/format-justify-fill.svg - ./graphics/Breeze/actions/32/go-down.svg + ./graphics/Breeze/actions/22/format-text-bold.svg ./graphics/Breeze/actions/64/go-home.svg ./graphics/Breeze/actions/32/go-jump.svg ./graphics/Breeze/actions/32/go-next.svg ./graphics/Breeze/actions/32/go-previous.svg - ./graphics/Breeze/actions/32/go-up.svg ./graphics/Breeze/actions/22/gtk-edit.svg ./graphics/Breeze/actions/32/help-about.svg ./graphics/Breeze/actions/22/help-contents.svg @@ -75,7 +76,9 @@ ./graphics/Breeze Dark/categories/32/applications-science.svg ./graphics/Breeze Dark/categories/32/applications-system.svg ./graphics/Breeze Dark/actions/32/arrow-down.svg + ./graphics/Breeze Dark/actions/32/arrow-down-double.svg ./graphics/Breeze Dark/actions/32/arrow-up.svg + ./graphics/Breeze Dark/actions/32/arrow-up-double.svg ./graphics/Breeze Dark/actions/32/call-start.svg ./graphics/Breeze Dark/status/64/dialog-error.svg ./graphics/Breeze Dark/status/64/dialog-information.svg @@ -98,12 +101,11 @@ ./graphics/Breeze Dark/places/96/folder.svg ./graphics/Breeze Dark/actions/22/format-indent-more.svg ./graphics/Breeze Dark/actions/22/format-justify-fill.svg - ./graphics/Breeze Dark/actions/32/go-down.svg + ./graphics/Breeze Dark/actions/22/format-text-bold.svg ./graphics/Breeze Dark/actions/64/go-home.svg ./graphics/Breeze Dark/actions/32/go-jump.svg ./graphics/Breeze Dark/actions/32/go-next.svg ./graphics/Breeze Dark/actions/32/go-previous.svg - ./graphics/Breeze Dark/actions/32/go-up.svg ./graphics/Breeze Dark/actions/22/gtk-edit.svg ./graphics/Breeze Dark/actions/32/help-about.svg ./graphics/Breeze Dark/actions/22/help-contents.svg @@ -166,12 +168,11 @@ ./graphics/Faenza/places/64/folder.png ./graphics/Faenza/actions/64/format-indent-more.png ./graphics/Faenza/actions/64/format-justify-fill.png - ./graphics/Faenza/actions/64/go-down.png + ./graphics/Faenza/actions/64/format-text-bold.png ./graphics/Faenza/actions/64/go-home.png ./graphics/Faenza/actions/64/go-jump.png ./graphics/Faenza/actions/64/go-next.png ./graphics/Faenza/actions/64/go-previous.png - ./graphics/Faenza/actions/64/go-up.png ./graphics/Faenza/actions/64/gtk-edit.png ./graphics/Faenza/actions/64/help-about.png ./graphics/Faenza/actions/64/help-contents.png @@ -212,7 +213,9 @@ ./graphics/Numix/22/categories/applications-science.svg ./graphics/Numix/22/categories/applications-system.svg ./graphics/Numix/22/actions/arrow-down.svg + ./graphics/Numix/22/actions/arrow-down-double.svg ./graphics/Numix/22/actions/arrow-up.svg + ./graphics/Numix/22/actions/arrow-up-double.svg ./graphics/Numix/22/actions/call-start.svg ./graphics/Numix/22/status/dialog-error.svg ./graphics/Numix/22/status/dialog-information.svg @@ -238,12 +241,11 @@ ./graphics/Numix/22/places/folder.svg ./graphics/Numix/22/actions/format-indent-more.svg ./graphics/Numix/22/actions/format-justify-fill.svg - ./graphics/Numix/22/actions/go-down.svg + ./graphics/Numix/22/actions/format-text-bold.svg ./graphics/Numix/22/actions/go-home.svg ./graphics/Numix/22/actions/go-jump.svg ./graphics/Numix/22/actions/go-next.svg ./graphics/Numix/22/actions/go-previous.svg - ./graphics/Numix/22/actions/go-up.svg ./graphics/Numix/22/actions/gtk-edit.svg ./graphics/Numix/22/categories/help-about.svg ./graphics/Numix/22/actions/help-contents.svg diff --git a/resources/sql/db_init_sqlite.sql b/resources/sql/db_init_sqlite.sql index 5fd1b067b..867eb8ab8 100644 --- a/resources/sql/db_init_sqlite.sql +++ b/resources/sql/db_init_sqlite.sql @@ -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, diff --git a/resources/sql/db_update_mysql_1_2.sql b/resources/sql/db_update_mysql_1_2.sql index 1b825cb30..8f00468ef 100755 --- a/resources/sql/db_update_mysql_1_2.sql +++ b/resources/sql/db_update_mysql_1_2.sql @@ -1,3 +1,8 @@ USE ##; -- ! -!! db_update_sqlite_1_2.sql \ No newline at end of file +SET FOREIGN_KEY_CHECKS = 0; +-- ! +!! db_update_sqlite_1_2.sql +-- ! +SET FOREIGN_KEY_CHECKS = 1; +-- ! \ No newline at end of file diff --git a/resources/sql/db_update_sqlite_1_2.sql b/resources/sql/db_update_sqlite_1_2.sql index 71cd4727a..bbded1e20 100755 --- a/resources/sql/db_update_sqlite_1_2.sql +++ b/resources/sql/db_update_sqlite_1_2.sql @@ -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; \ No newline at end of file +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; \ No newline at end of file diff --git a/src/librssguard/core/feedsproxymodel.cpp b/src/librssguard/core/feedsproxymodel.cpp index 5af28fff5..f93c7b27c 100644 --- a/src/librssguard/core/feedsproxymodel.cpp +++ b/src/librssguard/core/feedsproxymodel.cpp @@ -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,14 +172,32 @@ bool FeedsProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right return sortOrder() == Qt::SortOrder::DescendingOrder; } else if (left_item->kind() == right_item->kind()) { - // Both items are of the same type. - if (left.column() == FDS_MODEL_COUNTS_INDEX) { - // User wants to sort according to counts. - return left_item->countOfUnreadMessages() < right_item->countOfUnreadMessages(); + if (m_sortAlphabetically) { + // Both items are of the same type. + if (left.column() == FDS_MODEL_COUNTS_INDEX) { + // User wants to sort according to counts. + return left_item->countOfUnreadMessages() < right_item->countOfUnreadMessages(); + } + else { + // In other cases, sort by title. + return QString::localeAwareCompare(left_item->title().toLower(), right_item->title().toLower()) < 0; + } } else { - // In other cases, sort by title. - return QString::localeAwareCompare(left_item->title().toLower(), right_item->title().toLower()) < 0; + // 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 { @@ -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; diff --git a/src/librssguard/core/feedsproxymodel.h b/src/librssguard/core/feedsproxymodel.h index bcea633df..d78a46053 100644 --- a/src/librssguard/core/feedsproxymodel.h +++ b/src/librssguard/core/feedsproxymodel.h @@ -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> m_hiddenIndices; QList m_priorities; }; diff --git a/src/librssguard/database/databasequeries.cpp b/src/librssguard/database/databasequeries.cpp index f348eb4d8..6770f41ca 100644 --- a/src/librssguard/database/databasequeries.cpp +++ b/src/librssguard/database/databasequeries.cpp @@ -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)) { diff --git a/src/librssguard/database/databasequeries.h b/src/librssguard/database/databasequeries.h index de06513ae..38aeb3487 100644 --- a/src/librssguard/database/databasequeries.h +++ b/src/librssguard/database/databasequeries.h @@ -108,7 +108,7 @@ class DatabaseQueries { static QList getAccounts(const QSqlDatabase& db, const QString& code, bool* ok = nullptr); template - 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& 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 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(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 -void DatabaseQueries::loadFromDatabase(ServiceRoot* root) { +void DatabaseQueries::loadRootFromDatabase(ServiceRoot* root) { QSqlDatabase database = qApp->database()->driver()->connection(root->metaObject()->className()); Assignment categories = DatabaseQueries::getCategories(database, root->accountId()); Assignment feeds = DatabaseQueries::getFeeds(database, qApp->feedReader()->messageFilters(), root->accountId()); diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h index 0b4a22892..d8580e99a 100644 --- a/src/librssguard/definitions/definitions.h +++ b/src/librssguard/definitions/definitions.h @@ -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 diff --git a/src/librssguard/gui/dialogs/formaddeditlabel.cpp b/src/librssguard/gui/dialogs/formaddeditlabel.cpp index f52f7f855..59524a787 100644 --- a/src/librssguard/gui/dialogs/formaddeditlabel.cpp +++ b/src/librssguard/gui/dialogs/formaddeditlabel.cpp @@ -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() { diff --git a/src/librssguard/gui/dialogs/formaddeditlabel.ui b/src/librssguard/gui/dialogs/formaddeditlabel.ui index be3f287e0..6a62a4c72 100644 --- a/src/librssguard/gui/dialogs/formaddeditlabel.ui +++ b/src/librssguard/gui/dialogs/formaddeditlabel.ui @@ -6,7 +6,7 @@ 0 0 - 224 + 270 97 diff --git a/src/librssguard/gui/dialogs/formmain.cpp b/src/librssguard/gui/dialogs/formmain.cpp index 11becc9de..3da2bc3ed 100644 --- a/src/librssguard/gui/dialogs/formmain.cpp +++ b/src/librssguard/gui/dialogs/formmain.cpp @@ -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 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 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, diff --git a/src/librssguard/gui/dialogs/formmain.ui b/src/librssguard/gui/dialogs/formmain.ui index ecd059638..61f646c69 100644 --- a/src/librssguard/gui/dialogs/formmain.ui +++ b/src/librssguard/gui/dialogs/formmain.ui @@ -107,6 +107,16 @@ &Add item + + + &Move + + + + + + + @@ -116,11 +126,13 @@ + + @@ -853,6 +865,37 @@ Open in internal browser (no new tab) + + + true + + + true + + + &Sort alphabetically + + + + + Move &up + + + + + Move to &top + + + + + Move &down + + + + + Move to &bottom + + diff --git a/src/librssguard/gui/feedsview.cpp b/src/librssguard/gui/feedsview.cpp index f9dbdf52a..eb26b2a09 100644 --- a/src/librssguard/gui/feedsview.cpp +++ b/src/librssguard/gui/feedsview.cpp @@ -257,12 +257,12 @@ void FeedsView::deleteSelectedItem() { if (selected_item->canBeDeleted()) { // Ask user first. if (MsgBox::show(qApp->mainFormWidget(), - QMessageBox::Icon::Question, - tr("Deleting \"%1\"").arg(selected_item->title()), - tr("You are about to completely delete item \"%1\".").arg(selected_item->title()), - tr("Are you sure?"), - QString(), QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, - QMessageBox::StandardButton::Yes) == QMessageBox::StandardButton::No) { + QMessageBox::Icon::Question, + tr("Deleting \"%1\"").arg(selected_item->title()), + tr("You are about to completely delete item \"%1\".").arg(selected_item->title()), + tr("Are you sure?"), + QString(), QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, + QMessageBox::StandardButton::Yes) == QMessageBox::StandardButton::No) { // User refused. qApp->feedUpdateLock()->unlock(); return; @@ -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); diff --git a/src/librssguard/gui/feedsview.h b/src/librssguard/gui/feedsview.h index 6c2828226..c041b8e4d 100644 --- a/src/librssguard/gui/feedsview.h +++ b/src/librssguard/gui/feedsview.h @@ -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: diff --git a/src/librssguard/gui/guiutilities.cpp b/src/librssguard/gui/guiutilities.cpp index 4faea8474..46b227945 100644 --- a/src/librssguard/gui/guiutilities.cpp +++ b/src/librssguard/gui/guiutilities.cpp @@ -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); diff --git a/src/librssguard/gui/tabbar.cpp b/src/librssguard/gui/tabbar.cpp index 84d932c67..c33db4c90 100644 --- a/src/librssguard/gui/tabbar.cpp +++ b/src/librssguard/gui/tabbar.cpp @@ -11,7 +11,7 @@ #include TabBar::TabBar(QWidget* parent) : QTabBar(parent) { - setDocumentMode(false); + setDocumentMode(true); setUsesScrollButtons(true); setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); } diff --git a/src/librssguard/miscellaneous/application.cpp b/src/librssguard/miscellaneous/application.cpp index bad0ddc2b..cb9ea20c0 100644 --- a/src/librssguard/miscellaneous/application.cpp +++ b/src/librssguard/miscellaneous/application.cpp @@ -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(); } diff --git a/src/librssguard/miscellaneous/application.h b/src/librssguard/miscellaneous/application.h index 761ccbb6e..4f83c4386 100644 --- a/src/librssguard/miscellaneous/application.h +++ b/src/librssguard/miscellaneous/application.h @@ -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& pkgs, const QString& error); diff --git a/src/librssguard/miscellaneous/settings.cpp b/src/librssguard/miscellaneous/settings.cpp index 851e17ec9..337a58ecd 100644 --- a/src/librssguard/miscellaneous/settings.cpp +++ b/src/librssguard/miscellaneous/settings.cpp @@ -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; diff --git a/src/librssguard/miscellaneous/settings.h b/src/librssguard/miscellaneous/settings.h index 710654ebc..016a98210 100644 --- a/src/librssguard/miscellaneous/settings.h +++ b/src/librssguard/miscellaneous/settings.h @@ -93,6 +93,9 @@ namespace Feeds { KEY ShowOnlyUnreadFeeds; VALUE(bool) ShowOnlyUnreadFeedsDef; + KEY SortAlphabetically; + VALUE(bool) SortAlphabeticallyDef; + KEY ShowTreeBranches; VALUE(bool) ShowTreeBranchesDef; diff --git a/src/librssguard/miscellaneous/skinfactory.cpp b/src/librssguard/miscellaneous/skinfactory.cpp index 3ae86cf81..e82cf86bf 100644 --- a/src/librssguard/miscellaneous/skinfactory.cpp +++ b/src/librssguard/miscellaneous/skinfactory.cpp @@ -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) && diff --git a/src/librssguard/miscellaneous/skinfactory.h b/src/librssguard/miscellaneous/skinfactory.h index adeb82597..50a65bbf9 100644 --- a/src/librssguard/miscellaneous/skinfactory.h +++ b/src/librssguard/miscellaneous/skinfactory.h @@ -89,9 +89,9 @@ class RSSGUARD_DLLSPEC SkinFactory : public QObject { bool styleIsFrozen() const; -private: + 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; diff --git a/src/librssguard/services/abstract/rootitem.cpp b/src/librssguard/services/abstract/rootitem.cpp index dcc80dfa0..62e8ed793 100644 --- a/src/librssguard/services/abstract/rootitem.cpp +++ b/src/librssguard/services/abstract/rootitem.cpp @@ -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()), m_parentItem(parent_item) {} + m_keepOnTop(false), m_sortOrder(0), m_childItems(QList()), 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 RootItem::contextMenuFeedsList() { - return QList(); + 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); diff --git a/src/librssguard/services/abstract/rootitem.h b/src/librssguard/services/abstract/rootitem.h index a0b7f27cf..e6a7d27b3 100644 --- a/src/librssguard/services/abstract/rootitem.h +++ b/src/librssguard/services/abstract/rootitem.h @@ -90,12 +90,16 @@ class RSSGUARD_DLLSPEC RootItem : public QObject { virtual QList 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 m_childItems; RootItem* m_parentItem; }; diff --git a/src/librssguard/services/abstract/serviceroot.cpp b/src/librssguard/services/abstract/serviceroot.cpp index dc6b4b3e2..5f08a449b 100644 --- a/src/librssguard/services/abstract/serviceroot.cpp +++ b/src/librssguard/services/abstract/serviceroot.cpp @@ -77,7 +77,18 @@ bool ServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const { } QList 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 ServiceRoot::contextMenuMessagesList(const QList& messages) { diff --git a/src/librssguard/services/feedly/feedlyserviceroot.cpp b/src/librssguard/services/feedly/feedlyserviceroot.cpp index 22fa40f45..e6923e380 100644 --- a/src/librssguard/services/feedly/feedlyserviceroot.cpp +++ b/src/librssguard/services/feedly/feedlyserviceroot.cpp @@ -88,7 +88,7 @@ QList FeedlyServiceRoot::obtainNewMessages(Feed* feed, void FeedlyServiceRoot::start(bool freshly_activated) { if (!freshly_activated) { - DatabaseQueries::loadFromDatabase(this); + DatabaseQueries::loadRootFromDatabase(this); loadCacheFromFile(); } diff --git a/src/librssguard/services/gmail/gmailserviceroot.cpp b/src/librssguard/services/gmail/gmailserviceroot.cpp index 51825cc95..0aa104d68 100644 --- a/src/librssguard/services/gmail/gmailserviceroot.cpp +++ b/src/librssguard/services/gmail/gmailserviceroot.cpp @@ -163,7 +163,7 @@ bool GmailServiceRoot::supportsCategoryAdding() const { void GmailServiceRoot::start(bool freshly_activated) { if (!freshly_activated) { - DatabaseQueries::loadFromDatabase(this); + DatabaseQueries::loadRootFromDatabase(this); loadCacheFromFile(); } diff --git a/src/librssguard/services/greader/greadernetwork.cpp b/src/librssguard/services/greader/greadernetwork.cpp index 0a59ec5e4..e6b70b653 100644 --- a/src/librssguard/services/greader/greadernetwork.cpp +++ b/src/librssguard/services/greader/greadernetwork.cpp @@ -1012,7 +1012,7 @@ QList 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(); diff --git a/src/librssguard/services/greader/greaderserviceroot.cpp b/src/librssguard/services/greader/greaderserviceroot.cpp index c3ff7d997..4bb309772 100644 --- a/src/librssguard/services/greader/greaderserviceroot.cpp +++ b/src/librssguard/services/greader/greaderserviceroot.cpp @@ -155,7 +155,7 @@ bool GreaderServiceRoot::wantsBaggedIdsOfExistingMessages() const { void GreaderServiceRoot::start(bool freshly_activated) { if (!freshly_activated) { - DatabaseQueries::loadFromDatabase(this); + DatabaseQueries::loadRootFromDatabase(this); loadCacheFromFile(); } diff --git a/src/librssguard/services/newsblur/newsblurserviceroot.cpp b/src/librssguard/services/newsblur/newsblurserviceroot.cpp index 9576accfc..a57103999 100755 --- a/src/librssguard/services/newsblur/newsblurserviceroot.cpp +++ b/src/librssguard/services/newsblur/newsblurserviceroot.cpp @@ -72,7 +72,7 @@ QList NewsBlurServiceRoot::obtainNewMessages(Feed* feed, void NewsBlurServiceRoot::start(bool freshly_activated) { if (!freshly_activated) { - DatabaseQueries::loadFromDatabase(this); + DatabaseQueries::loadRootFromDatabase(this); loadCacheFromFile(); } diff --git a/src/librssguard/services/owncloud/owncloudserviceroot.cpp b/src/librssguard/services/owncloud/owncloudserviceroot.cpp index 13d54e664..8992c1d23 100644 --- a/src/librssguard/services/owncloud/owncloudserviceroot.cpp +++ b/src/librssguard/services/owncloud/owncloudserviceroot.cpp @@ -50,7 +50,7 @@ bool OwnCloudServiceRoot::supportsCategoryAdding() const { void OwnCloudServiceRoot::start(bool freshly_activated) { if (!freshly_activated) { - DatabaseQueries::loadFromDatabase(this); + DatabaseQueries::loadRootFromDatabase(this); loadCacheFromFile(); } diff --git a/src/librssguard/services/reddit/redditserviceroot.cpp b/src/librssguard/services/reddit/redditserviceroot.cpp index 3e6c921fc..fbf8d232f 100644 --- a/src/librssguard/services/reddit/redditserviceroot.cpp +++ b/src/librssguard/services/reddit/redditserviceroot.cpp @@ -94,7 +94,7 @@ bool RedditServiceRoot::supportsCategoryAdding() const { void RedditServiceRoot::start(bool freshly_activated) { if (!freshly_activated) { - DatabaseQueries::loadFromDatabase(this); + DatabaseQueries::loadRootFromDatabase(this); loadCacheFromFile(); } diff --git a/src/librssguard/services/standard/parsers/feedparser.cpp b/src/librssguard/services/standard/parsers/feedparser.cpp index 7a4c17dfb..834d98787 100644 --- a/src/librssguard/services/standard/parsers/feedparser.cpp +++ b/src/librssguard/services/standard/parsers/feedparser.cpp @@ -83,7 +83,7 @@ QString FeedParser::jsonMessageRawContents(const QJsonObject& msg_element) const QList FeedParser::messages() { QString feed_author = feedAuthor(); QList messages; - QDateTime current_time = QDateTime::currentDateTime(); + QDateTime current_time = QDateTime::currentDateTimeUtc(); // Pull out all messages. if (m_isXml) { diff --git a/src/librssguard/services/standard/standardserviceroot.cpp b/src/librssguard/services/standard/standardserviceroot.cpp index 37bf2956f..5754131bf 100644 --- a/src/librssguard/services/standard/standardserviceroot.cpp +++ b/src/librssguard/services/standard/standardserviceroot.cpp @@ -50,7 +50,7 @@ StandardServiceRoot::~StandardServiceRoot() { } void StandardServiceRoot::start(bool freshly_activated) { - DatabaseQueries::loadFromDatabase(this); + DatabaseQueries::loadRootFromDatabase(this); if (freshly_activated && getSubTreeFeeds().isEmpty()) { // In other words, if there are no feeds or categories added. diff --git a/src/librssguard/services/tt-rss/ttrssserviceroot.cpp b/src/librssguard/services/tt-rss/ttrssserviceroot.cpp index 5088524ad..c4c224023 100644 --- a/src/librssguard/services/tt-rss/ttrssserviceroot.cpp +++ b/src/librssguard/services/tt-rss/ttrssserviceroot.cpp @@ -41,7 +41,7 @@ ServiceRoot::LabelOperation TtRssServiceRoot::supportedLabelOperations() const { void TtRssServiceRoot::start(bool freshly_activated) { if (!freshly_activated) { - DatabaseQueries::loadFromDatabase(this); + DatabaseQueries::loadRootFromDatabase(this); loadCacheFromFile(); auto lbls = m_labelsNode->labels(); diff --git a/src/rssguard/main.cpp b/src/rssguard/main.cpp index 55cff7c7e..7b8d5506c 100644 --- a/src/rssguard/main.cpp +++ b/src/rssguard/main.cpp @@ -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();