From 623caa76316000b791b0e324c12087ca9a938e91 Mon Sep 17 00:00:00 2001 From: martinrotter Date: Mon, 30 Oct 2023 10:54:44 +0100 Subject: [PATCH] Multi select feeds list - basic implementation (#1148) * Starting to work on this. * Working, saving of common data in feeds could work * Working, saving of common data in feeds could work * work on standard * save work * work on multi feed select, should work now for feeds * kind of works, there are some corner cases and UX things that need to be sorted out, will merge so people can test --- resources/icons.qrc | 2 + src/librssguard/CMakeLists.txt | 5 +- src/librssguard/gui/dialogs/formmain.cpp | 2 + src/librssguard/gui/feedsview.cpp | 114 +++++++- src/librssguard/gui/feedsview.h | 6 +- .../gui/reusable/discoverfeedsbutton.cpp | 81 ------ .../gui/reusable/discoverfeedsbutton.h | 26 -- .../gui/toolbars/toolbareditor.cpp | 2 +- src/librssguard/gui/webbrowser.cpp | 19 -- src/librssguard/gui/webbrowser.h | 3 - src/librssguard/services/abstract/feed.cpp | 7 - src/librssguard/services/abstract/feed.h | 7 +- .../abstract/gui/authenticationdetails.ui | 125 +++++---- .../abstract/gui/formcategorydetails.cpp | 98 +++++-- .../abstract/gui/formcategorydetails.h | 54 ++-- .../abstract/gui/formcategorydetails.ui | 179 ++++++++----- .../services/abstract/gui/formfeeddetails.cpp | 154 +++++++---- .../services/abstract/gui/formfeeddetails.h | 53 ++-- .../services/abstract/gui/formfeeddetails.ui | 247 +++++++++++------- .../abstract/gui/multifeededitcheckbox.cpp | 22 ++ .../abstract/gui/multifeededitcheckbox.h | 21 ++ src/librssguard/services/abstract/label.cpp | 13 - src/librssguard/services/abstract/label.h | 1 - .../services/abstract/rootitem.cpp | 4 - src/librssguard/services/abstract/rootitem.h | 16 +- src/librssguard/services/abstract/search.cpp | 22 -- src/librssguard/services/abstract/search.h | 1 - .../services/abstract/serviceroot.cpp | 104 ++++++++ .../services/abstract/serviceroot.h | 4 + .../services/feedly/feedlyserviceroot.cpp | 16 +- .../services/feedly/feedlyserviceroot.h | 5 +- .../services/gmail/gmailserviceroot.cpp | 16 +- .../services/gmail/gmailserviceroot.h | 3 +- .../services/greader/greaderserviceroot.cpp | 16 +- .../services/greader/greaderserviceroot.h | 3 +- .../services/owncloud/owncloudserviceroot.cpp | 16 +- .../services/owncloud/owncloudserviceroot.h | 5 +- .../services/reddit/redditserviceroot.cpp | 16 +- .../services/reddit/redditserviceroot.h | 5 +- .../standard/gui/formdiscoverfeeds.cpp | 7 +- .../standard/gui/formeditstandardaccount.h | 2 + .../standard/gui/formstandardfeeddetails.cpp | 115 ++++++-- .../standard/gui/standardfeeddetails.cpp | 1 + .../standard/gui/standardfeeddetails.ui | 176 +++++++++---- .../services/standard/standardcategory.cpp | 9 - .../services/standard/standardcategory.h | 3 +- .../services/standard/standardfeed.cpp | 13 - .../services/standard/standardfeed.h | 1 - .../services/standard/standardserviceroot.cpp | 35 ++- .../services/standard/standardserviceroot.h | 3 +- .../services/tt-rss/ttrssserviceroot.cpp | 16 +- .../services/tt-rss/ttrssserviceroot.h | 3 +- 52 files changed, 1218 insertions(+), 659 deletions(-) delete mode 100644 src/librssguard/gui/reusable/discoverfeedsbutton.cpp delete mode 100644 src/librssguard/gui/reusable/discoverfeedsbutton.h create mode 100644 src/librssguard/services/abstract/gui/multifeededitcheckbox.cpp create mode 100644 src/librssguard/services/abstract/gui/multifeededitcheckbox.h diff --git a/resources/icons.qrc b/resources/icons.qrc index 2d41be1a3..f8a10e675 100644 --- a/resources/icons.qrc +++ b/resources/icons.qrc @@ -27,6 +27,7 @@ ./graphics/Breeze/actions/32/document-open.svg ./graphics/Breeze/actions/32/document-revert.svg ./graphics/Breeze/actions/22/download.svg + ./graphics/Breeze/actions/22/draw-line.svg ./graphics/Breeze/actions/22/edit-clear.svg ./graphics/Breeze/actions/22/edit-copy.svg ./graphics/Breeze/actions/22/edit-cut.svg @@ -120,6 +121,7 @@ ./graphics/Breeze Dark/actions/32/document-open.svg ./graphics/Breeze Dark/actions/32/document-revert.svg ./graphics/Breeze Dark/actions/22/download.svg + ./graphics/Breeze Dark/actions/22/draw-line.svg ./graphics/Breeze Dark/actions/22/edit-clear.svg ./graphics/Breeze Dark/actions/22/edit-copy.svg ./graphics/Breeze Dark/actions/22/edit-cut.svg diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index 020343dc8..6c4bf3852 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -117,8 +117,6 @@ set(SOURCES gui/reusable/colortoolbutton.h gui/reusable/comboboxwithstatus.cpp gui/reusable/comboboxwithstatus.h - gui/reusable/discoverfeedsbutton.cpp - gui/reusable/discoverfeedsbutton.h gui/reusable/edittableview.cpp gui/reusable/edittableview.h gui/reusable/helpspoiler.cpp @@ -296,6 +294,8 @@ set(SOURCES services/abstract/gui/formcategorydetails.h services/abstract/gui/formfeeddetails.cpp services/abstract/gui/formfeeddetails.h + services/abstract/gui/multifeededitcheckbox.cpp + services/abstract/gui/multifeededitcheckbox.h services/abstract/importantnode.cpp services/abstract/importantnode.h services/abstract/label.cpp @@ -674,6 +674,7 @@ target_include_directories(rssguard ${CMAKE_CURRENT_SOURCE_DIR}/gui ${CMAKE_CURRENT_SOURCE_DIR}/gui/dialogs ${CMAKE_CURRENT_SOURCE_DIR}/gui/reusable + ${CMAKE_CURRENT_SOURCE_DIR}/services/abstract/gui ${CMAKE_CURRENT_SOURCE_DIR}/dynamic-shortcuts PRIVATE diff --git a/src/librssguard/gui/dialogs/formmain.cpp b/src/librssguard/gui/dialogs/formmain.cpp index 68bb9aa31..b4cad0e99 100644 --- a/src/librssguard/gui/dialogs/formmain.cpp +++ b/src/librssguard/gui/dialogs/formmain.cpp @@ -231,6 +231,8 @@ QList FormMain::allActions() const { actions << m_ui->m_actionExpandCollapseItem; actions << m_ui->m_actionExpandCollapseItemRecursively; actions << m_ui->m_actionMessageFilters; + actions << m_ui->m_actionEmptyAllRecycleBins; + actions << m_ui->m_actionRestoreAllRecycleBins; actions << m_ui->m_actionTabNewWebBrowser; actions << m_ui->m_actionTabsCloseCurrent; actions << m_ui->m_actionTabsCloseAll; diff --git a/src/librssguard/gui/feedsview.cpp b/src/librssguard/gui/feedsview.cpp index 88b41abb1..a2e8de708 100644 --- a/src/librssguard/gui/feedsview.cpp +++ b/src/librssguard/gui/feedsview.cpp @@ -2,6 +2,7 @@ #include "gui/feedsview.h" +#include "3rd-party/boolinq/boolinq.h" #include "core/feedsmodel.h" #include "core/feedsproxymodel.h" #include "definitions/definitions.h" @@ -13,6 +14,7 @@ #include "miscellaneous/settings.h" #include "miscellaneous/textfactory.h" #include "services/abstract/feed.h" +#include "services/abstract/gui/formaccountdetails.h" #include "services/abstract/rootitem.h" #include "services/abstract/serviceroot.h" @@ -24,6 +26,8 @@ #include #include +#include + FeedsView::FeedsView(QWidget* parent) : BaseTreeView(parent), m_contextMenuService(nullptr), m_contextMenuBin(nullptr), m_contextMenuCategories(nullptr), m_contextMenuFeeds(nullptr), m_contextMenuImportant(nullptr), m_contextMenuEmptySpace(nullptr), @@ -80,6 +84,7 @@ QList FeedsView::selectedFeeds() const { RootItem* FeedsView::selectedItem() const { const QModelIndexList selected_rows = selectionModel()->selectedRows(); + const QModelIndex current_row = currentIndex(); if (selected_rows.isEmpty()) { return nullptr; @@ -87,10 +92,39 @@ RootItem* FeedsView::selectedItem() const { else { RootItem* selected_item = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(selected_rows.at(0))); - return selected_item == m_sourceModel->rootItem() ? nullptr : selected_item; + if (selected_rows.size() == 1) { + return selected_item; + } + + auto selected_items = boolinq::from(selected_rows) + .select([this](const QModelIndex& idx) { + return m_sourceModel->itemForIndex(m_proxyModel->mapToSource(idx)); + }) + .toStdList(); + + RootItem* current_item = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(current_row)); + + if (std::find(selected_items.begin(), selected_items.end(), current_item) != selected_items.end()) { + return current_item; + } + else { + return selected_items.front(); + } } } +QList FeedsView::selectedItems() const { + const QModelIndexList selected_rows = selectionModel()->selectedRows(); + + auto selected_items = boolinq::from(selected_rows) + .select([this](const QModelIndex& idx) { + return m_sourceModel->itemForIndex(m_proxyModel->mapToSource(idx)); + }) + .toStdList(); + + return FROM_STD_LIST(QList, selected_items); +} + void FeedsView::copyUrlOfSelectedFeeds() const { auto feeds = selectedFeeds(); QStringList urls; @@ -218,16 +252,84 @@ void FeedsView::editSelectedItem() { return; } - if (selectedItem()->canBeEdited()) { - selectedItem()->editViaGui(); + auto selected_items = selectedItems(); + + if (selected_items.isEmpty()) { + qApp->feedUpdateLock()->unlock(); + return; } - else { + + auto std_editable_items = boolinq::from(selected_items) + .where([](RootItem* it) { + return it->canBeEdited(); + }) + .toStdList(); + + if (std_editable_items.empty()) { qApp->showGuiMessage(Notification::Event::GeneralEvent, - {tr("Cannot edit item"), - tr("Selected item cannot be edited, this is not (yet?) supported."), + {tr("Cannot edit items"), + tr("Selected items cannot be edited. This is not supported (yet)."), + QSystemTrayIcon::MessageIcon::Critical}); + + qApp->feedUpdateLock()->unlock(); + return; + } + + if (std_editable_items.front()->kind() == RootItem::Kind::ServiceRoot && std_editable_items.size() > 1) { + qApp->showGuiMessage(Notification::Event::GeneralEvent, + {tr("Cannot edit items"), + tr("%1 does not support batch editing of multiple accounts.").arg(QSL(APP_NAME)), + QSystemTrayIcon::MessageIcon::Critical}); + + qApp->feedUpdateLock()->unlock(); + return; + } + + // We also check if items are from single account, if not we end. + std::list distinct_accounts = boolinq::from(std_editable_items) + .select([](RootItem* it) { + return it->getParentServiceRoot(); + }) + .distinct() + .toStdList(); + + if (distinct_accounts.size() != 1) { + qApp->showGuiMessage(Notification::Event::GeneralEvent, + {tr("Cannot edit items"), + tr("%1 does not support batch editing of items from multiple accounts.").arg(QSL(APP_NAME)), + QSystemTrayIcon::MessageIcon::Critical}); + + qApp->feedUpdateLock()->unlock(); + return; + } + + std::list distinct_types = boolinq::from(std_editable_items) + .select([](RootItem* it) { + return it->kind(); + }) + .distinct() + .toStdList(); + + if (distinct_types.size() != 1) { + qApp->showGuiMessage(Notification::Event::GeneralEvent, + {tr("Cannot edit items"), + tr("%1 does not support batch editing of items of varying types.").arg(QSL(APP_NAME)), + QSystemTrayIcon::MessageIcon::Critical}); + + qApp->feedUpdateLock()->unlock(); + return; + } + + if (qsizetype(std_editable_items.size()) < selected_items.size()) { + // Some items are not editable. + qApp->showGuiMessage(Notification::Event::GeneralEvent, + {tr("Cannot edit some items"), + tr("Some of selected items cannot be edited. Proceeding to edit the rest."), QSystemTrayIcon::MessageIcon::Warning}); } + distinct_accounts.front()->editItemsViaGui(FROM_STD_LIST(QList, std_editable_items)); + // Changes are done, unlock the update master lock. qApp->feedUpdateLock()->unlock(); diff --git a/src/librssguard/gui/feedsview.h b/src/librssguard/gui/feedsview.h index 8836531be..403e79a48 100644 --- a/src/librssguard/gui/feedsview.h +++ b/src/librssguard/gui/feedsview.h @@ -32,9 +32,11 @@ class RSSGUARD_DLLSPEC FeedsView : public BaseTreeView { // NOTE: This is recursive method which returns all descendants. QList selectedFeeds() const; - // Returns pointers to selected feed/category if they are really - // selected. + // Returns selected item. If multiple items are selected, returns + // the one of them which is also "current". If none of them is + // "current", returns firs item of selected ones. RootItem* selectedItem() const; + QList selectedItems() const; // Saves/loads expand states of all nodes (feeds/categories) of the list to/from settings. void saveAllExpandStates(); diff --git a/src/librssguard/gui/reusable/discoverfeedsbutton.cpp b/src/librssguard/gui/reusable/discoverfeedsbutton.cpp deleted file mode 100644 index 5f257c00e..000000000 --- a/src/librssguard/gui/reusable/discoverfeedsbutton.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#include "gui/reusable/discoverfeedsbutton.h" - -#include "core/feedsmodel.h" -#include "gui/dialogs/formmain.h" -#include "gui/feedmessageviewer.h" -#include "gui/feedsview.h" -#include "gui/tabwidget.h" -#include "miscellaneous/application.h" -#include "miscellaneous/feedreader.h" -#include "miscellaneous/iconfactory.h" -#include "services/abstract/serviceroot.h" - -#include - -DiscoverFeedsButton::DiscoverFeedsButton(QWidget* parent) : QToolButton(parent), m_addresses(QStringList()) { - setEnabled(false); - setIcon(qApp->icons()->fromTheme(QSL("application-rss+xml"))); - setPopupMode(QToolButton::ToolButtonPopupMode::InstantPopup); -} - -DiscoverFeedsButton::~DiscoverFeedsButton() {} - -void DiscoverFeedsButton::clearFeedAddresses() { - setFeedAddresses({}); -} - -void DiscoverFeedsButton::setFeedAddresses(const QStringList& addresses) { - setEnabled(!addresses.isEmpty()); - setToolTip(addresses.isEmpty() ? - tr("This website does not contain any feeds") : - tr("Add one of %n feed(s)", 0, addresses.size())); - - if (menu() == nullptr) { - // Initialize the menu. - setMenu(new QMenu(this)); - connect(menu(), &QMenu::triggered, this, &DiscoverFeedsButton::linkTriggered); - connect(menu(), &QMenu::aboutToShow, this, &DiscoverFeedsButton::fillMenu); - } - - menu()->hide(); - m_addresses = addresses; -} - -void DiscoverFeedsButton::linkTriggered(QAction* action) { - const QString url = action->property("url").toString(); - ServiceRoot* root = static_cast(action->property("root").value()); - - if (root->supportsFeedAdding()) { - root->addNewFeed(qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->selectedItem(), url); - } - else { - qApp->showGuiMessage(Notification::Event::GeneralEvent, { - tr("Not supported by account"), - tr("Given account does not support adding feeds."), - QSystemTrayIcon::MessageIcon::Warning }); - } -} - -void DiscoverFeedsButton::fillMenu() { - menu()->clear(); - auto srts = qApp->feedReader()->feedsModel()->serviceRoots(); - - for (const ServiceRoot* root : qAsConst(srts)) { - if (root->supportsFeedAdding()) { - QMenu* root_menu = menu()->addMenu(root->icon(), root->title()); - - for (const QString& url : qAsConst(m_addresses)) { - QAction* url_action = root_menu->addAction(root->icon(), url); - - url_action->setProperty("url", url); - url_action->setProperty("root", QVariant::fromValue((void*) root)); - } - } - } - - if (menu()->isEmpty()) { - menu()->addAction(tr("Feeds were detected, but no suitable accounts are configured."))->setEnabled(false); - } -} diff --git a/src/librssguard/gui/reusable/discoverfeedsbutton.h b/src/librssguard/gui/reusable/discoverfeedsbutton.h deleted file mode 100644 index 5f21f5e2e..000000000 --- a/src/librssguard/gui/reusable/discoverfeedsbutton.h +++ /dev/null @@ -1,26 +0,0 @@ -// For license of this file, see /LICENSE.md. - -#ifndef DISCOVERFEEDSBUTTON_H -#define DISCOVERFEEDSBUTTON_H - -#include - -class DiscoverFeedsButton : public QToolButton { - Q_OBJECT - - public: - explicit DiscoverFeedsButton(QWidget* parent = nullptr); - virtual ~DiscoverFeedsButton(); - - void clearFeedAddresses(); - void setFeedAddresses(const QStringList& addresses); - - private slots: - void linkTriggered(QAction* action); - void fillMenu(); - - private: - QStringList m_addresses; -}; - -#endif // DISCOVERFEEDSBUTTON_H diff --git a/src/librssguard/gui/toolbars/toolbareditor.cpp b/src/librssguard/gui/toolbars/toolbareditor.cpp index 24278269e..52cbaefe0 100644 --- a/src/librssguard/gui/toolbars/toolbareditor.cpp +++ b/src/librssguard/gui/toolbars/toolbareditor.cpp @@ -80,7 +80,7 @@ void ToolBarEditor::loadEditor(const QList& activated_actions, const Q if (action->isSeparator()) { action_item->setData(Qt::ItemDataRole::UserRole, SEPARATOR_ACTION_NAME); - action_item->setIcon(qApp->icons()->fromTheme(QSL("insert-object"))); + action_item->setIcon(qApp->icons()->fromTheme(QSL("draw-line"), QSL("insert-object"))); action_item->setText(tr("Separator")); action_item->setToolTip(tr("Separator")); } diff --git a/src/librssguard/gui/webbrowser.cpp b/src/librssguard/gui/webbrowser.cpp index 4ea62b274..6e1c91532 100644 --- a/src/librssguard/gui/webbrowser.cpp +++ b/src/librssguard/gui/webbrowser.cpp @@ -5,7 +5,6 @@ #include "definitions/globals.h" #include "gui/dialogs/formmain.h" #include "gui/messagebox.h" -#include "gui/reusable/discoverfeedsbutton.h" #include "gui/reusable/locationlineedit.h" #include "gui/reusable/searchtextwidget.h" #include "gui/tabwidget.h" @@ -27,7 +26,6 @@ WebBrowser::WebBrowser(WebViewer* viewer, QWidget* parent) : TabContent(parent), m_layout(new QVBoxLayout(this)), m_toolBar(new QToolBar(tr("Navigation panel"), this)), m_webView(viewer), m_searchWidget(new SearchTextWidget(this)), m_txtLocation(new LocationLineEdit(this)), - m_btnDiscoverFeeds(new DiscoverFeedsButton(this)), m_actionOpenInSystemBrowser(new QAction(qApp->icons()->fromTheme(QSL("document-open")), tr("Open this website in system web browser"), this)), @@ -297,15 +295,9 @@ void WebBrowser::initializeLayout() { m_actionReload->setIcon(qApp->icons()->fromTheme(QSL("reload"), QSL("view-refresh"))); m_actionStop->setIcon(qApp->icons()->fromTheme(QSL("process-stop"))); - m_btnDiscoverFeedsAction = new QWidgetAction(this); - m_actionOpenInSystemBrowser->setEnabled(false); m_actionReadabilePage->setEnabled(false); - // m_btnDiscoverFeedsAction->setDefaultWidget(new QWidget(this)); - - m_btnDiscoverFeedsAction->setDefaultWidget(m_btnDiscoverFeeds); - // Add needed actions into toolbar. m_toolBar->addAction(m_actionBack); m_toolBar->addAction(m_actionForward); @@ -314,7 +306,6 @@ void WebBrowser::initializeLayout() { m_toolBar->addAction(m_actionOpenInSystemBrowser); m_toolBar->addAction(m_actionReadabilePage); - m_toolBar->addAction(m_btnDiscoverFeedsAction); m_txtLocationAction = m_toolBar->addWidget(m_txtLocation); m_loadingProgress = new QProgressBar(this); @@ -336,7 +327,6 @@ void WebBrowser::initializeLayout() { } void WebBrowser::onLoadingStarted() { - m_btnDiscoverFeeds->clearFeedAddresses(); m_loadingProgress->show(); m_actionOpenInSystemBrowser->setEnabled(false); m_actionReadabilePage->setEnabled(false); @@ -359,15 +349,6 @@ void WebBrowser::onLoadingFinished(bool success) { m_actionOpenInSystemBrowser->setEnabled(false); m_actionReadabilePage->setEnabled(false); } - - // TODO: nevolat toto u internich "rssguard" adres - // Let's check if there are any feeds defined on the web and eventually - // display "Add feeds" button. - m_btnDiscoverFeeds->setFeedAddresses(NetworkFactory::extractFeedLinksFromHtmlPage(m_webView->url(), - m_webView->html())); - } - else { - m_btnDiscoverFeeds->clearFeedAddresses(); } m_loadingProgress->hide(); diff --git a/src/librssguard/gui/webbrowser.h b/src/librssguard/gui/webbrowser.h index 425660b96..fc0172622 100644 --- a/src/librssguard/gui/webbrowser.h +++ b/src/librssguard/gui/webbrowser.h @@ -22,7 +22,6 @@ class QLabel; class TabWidget; class WebViewer; class LocationLineEdit; -class DiscoverFeedsButton; class SearchTextWidget; class WebBrowser : public TabContent { @@ -91,8 +90,6 @@ class WebBrowser : public TabContent { SearchTextWidget* m_searchWidget; LocationLineEdit* m_txtLocation; QAction* m_txtLocationAction; - DiscoverFeedsButton* m_btnDiscoverFeeds; - QWidgetAction* m_btnDiscoverFeedsAction; QProgressBar* m_loadingProgress; QAction* m_actionBack; QAction* m_actionForward; diff --git a/src/librssguard/services/abstract/feed.cpp b/src/librssguard/services/abstract/feed.cpp index cdcc99199..7943b506c 100644 --- a/src/librssguard/services/abstract/feed.cpp +++ b/src/librssguard/services/abstract/feed.cpp @@ -150,13 +150,6 @@ bool Feed::canBeEdited() const { return true; } -bool Feed::editViaGui() { - QScopedPointer form_pointer(new FormFeedDetails(getParentServiceRoot(), qApp->mainFormWidget())); - - form_pointer->addEditFeed(this); - return false; -} - void Feed::setAutoUpdateInterval(int auto_update_interval) { // If new initial auto-update interval is set, then // we should reset time that remains to the next auto-update. diff --git a/src/librssguard/services/abstract/feed.h b/src/librssguard/services/abstract/feed.h index 922eeb6a8..4cbf1f432 100644 --- a/src/librssguard/services/abstract/feed.h +++ b/src/librssguard/services/abstract/feed.h @@ -17,7 +17,11 @@ class Feed : public RootItem { public: // Specifies the auto-download strategy for the feed. - enum class AutoUpdateType { DontAutoUpdate = 0, DefaultAutoUpdate = 1, SpecificAutoUpdate = 2 }; + enum class AutoUpdateType { + DontAutoUpdate = 0, + DefaultAutoUpdate = 1, + SpecificAutoUpdate = 2 + }; // Specifies the actual "status" of the feed. // For example if it has new messages, error @@ -47,7 +51,6 @@ class Feed : public RootItem { virtual QVariantHash customDatabaseData() const; virtual void setCustomDatabaseData(const QVariantHash& data); virtual bool canBeEdited() const; - virtual bool editViaGui(); virtual QVariant data(int column, int role) const; void setCountOfAllMessages(int count_all_messages); diff --git a/src/librssguard/services/abstract/gui/authenticationdetails.ui b/src/librssguard/services/abstract/gui/authenticationdetails.ui index 050b6302d..a0f264d67 100644 --- a/src/librssguard/services/abstract/gui/authenticationdetails.ui +++ b/src/librssguard/services/abstract/gui/authenticationdetails.ui @@ -7,73 +7,92 @@ 0 0 350 - 196 + 153 Form - - - - Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. - - - Credentials - - - false - - - false - - - - - - Username - - - m_txtUsername - - - - - - - - - - Password - - - m_txtPassword - - - - - - - - - - - - Authentication type - - - m_cbAuthType - - + + + + + + + + Authentication type + + + m_cbAuthType + + + + + + + + + + + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + + + Credentials + + + false + + + false + + + + + + Username + + + m_txtUsername + + + + + + + + + + Password + + + m_txtPassword + + + + + + + + + + + + + MultiFeedEditCheckBox + QCheckBox +
multifeededitcheckbox.h
+
LineEditWithStatus QWidget diff --git a/src/librssguard/services/abstract/gui/formcategorydetails.cpp b/src/librssguard/services/abstract/gui/formcategorydetails.cpp index b21507ac4..61216b752 100644 --- a/src/librssguard/services/abstract/gui/formcategorydetails.cpp +++ b/src/librssguard/services/abstract/gui/formcategorydetails.cpp @@ -10,6 +10,7 @@ #include "gui/reusable/baselineedit.h" #include "miscellaneous/iconfactory.h" #include "services/abstract/category.h" +#include "services/abstract/gui/multifeededitcheckbox.h" #include "services/abstract/rootitem.h" #include @@ -23,7 +24,7 @@ #include FormCategoryDetails::FormCategoryDetails(ServiceRoot* service_root, RootItem* parent_to_select, QWidget* parent) - : QDialog(parent), m_category(nullptr), m_serviceRoot(service_root), m_parentToSelect(parent_to_select) { + : QDialog(parent), m_serviceRoot(service_root), m_parentToSelect(parent_to_select) { initialize(); createConnections(); @@ -50,8 +51,28 @@ void FormCategoryDetails::createConnections() { connect(m_actionUseDefaultIcon, &QAction::triggered, this, &FormCategoryDetails::onUseDefaultIcon); } +bool FormCategoryDetails::isChangeAllowed(MultiFeedEditCheckBox* mcb) const { + return !m_isBatchEdit || mcb->isChecked(); +} + void FormCategoryDetails::loadCategoryData() { - loadCategories(m_serviceRoot->getSubTreeCategories(), m_serviceRoot, m_category); + Category* cat = category(); + + if (m_isBatchEdit) { + // We hook batch selectors. + m_ui->m_mcbParent->addActionWidget(m_ui->m_cmbParentCategory); + m_ui->m_mcbTitle->addActionWidget(m_ui->m_txtTitle); + m_ui->m_mcbDescription->addActionWidget(m_ui->m_txtDescription); + m_ui->m_mcbIcon->addActionWidget(m_ui->m_btnIcon); + } + else { + // We hide batch selectors. + for (auto* cb : findChildren()) { + cb->hide(); + } + } + + loadCategories(m_serviceRoot->getSubTreeCategories(), m_serviceRoot, cat); if (m_creatingNew) { GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("folder")), tr("Add new category")); @@ -76,42 +97,69 @@ void FormCategoryDetails::loadCategoryData() { } } else { - GuiUtilities::applyDialogProperties(*this, m_category->fullIcon(), tr("Edit \"%1\"").arg(m_category->title())); + if (!m_isBatchEdit) { + GuiUtilities::applyDialogProperties(*this, cat->fullIcon(), tr("Edit \"%1\"").arg(cat->title())); + } + else { + GuiUtilities::applyDialogProperties(*this, + qApp->icons()->fromTheme(QSL("folder")), + tr("Edit %n categories", nullptr, m_categories.size())); + } - m_ui->m_cmbParentCategory - ->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue(m_category->parent()))); + m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue(cat->parent()))); } - m_ui->m_txtTitle->lineEdit()->setText(m_category->title()); - m_ui->m_txtDescription->lineEdit()->setText(m_category->description()); - m_ui->m_btnIcon->setIcon(m_category->icon()); + m_ui->m_txtTitle->lineEdit()->setText(cat->title()); + m_ui->m_txtDescription->lineEdit()->setText(cat->description()); + m_ui->m_btnIcon->setIcon(cat->icon()); m_ui->m_txtTitle->lineEdit()->setFocus(); } void FormCategoryDetails::apply() { + QList cats = categories(); RootItem* parent = m_ui->m_cmbParentCategory->currentData().value(); - - m_category->setTitle(m_ui->m_txtTitle->lineEdit()->text()); - m_category->setDescription(m_ui->m_txtDescription->lineEdit()->text()); - m_category->setIcon(m_ui->m_btnIcon->icon()); - QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); - try { - DatabaseQueries::createOverwriteCategory(database, m_category, m_serviceRoot->accountId(), parent->id()); - } - catch (const ApplicationException& ex) { - qFatal("Cannot save category: '%s'.", qPrintable(ex.message())); - } - - m_serviceRoot->requestItemReassignment(m_category, parent); - m_serviceRoot->itemChanged({m_category}); - - if (m_creatingNew) { - m_serviceRoot->requestItemExpand({parent}, true); + for (Category* cat : cats) { + if (isChangeAllowed(m_ui->m_mcbTitle)) { + cat->setTitle(m_ui->m_txtTitle->lineEdit()->text()); + } + + if (isChangeAllowed(m_ui->m_mcbDescription)) { + cat->setDescription(m_ui->m_txtDescription->lineEdit()->text()); + } + + if (isChangeAllowed(m_ui->m_mcbIcon)) { + cat->setIcon(m_ui->m_btnIcon->icon()); + } + + int new_parent_id; + + if (isChangeAllowed(m_ui->m_mcbParent)) { + new_parent_id = parent->id(); + } + else { + new_parent_id = cat->parent()->id(); + } + + try { + DatabaseQueries::createOverwriteCategory(database, cat, m_serviceRoot->accountId(), new_parent_id); + } + catch (const ApplicationException& ex) { + qFatal("Cannot save category: '%s'.", qPrintable(ex.message())); + } + + if (isChangeAllowed(m_ui->m_mcbParent)) { + m_serviceRoot->requestItemReassignment(cat, parent); + } + + if (m_creatingNew) { + m_serviceRoot->requestItemExpand({parent}, true); + } } + m_serviceRoot->itemChanged(categories()); accept(); } diff --git a/src/librssguard/services/abstract/gui/formcategorydetails.h b/src/librssguard/services/abstract/gui/formcategorydetails.h index 218e80ec5..82ca17a6a 100644 --- a/src/librssguard/services/abstract/gui/formcategorydetails.h +++ b/src/librssguard/services/abstract/gui/formcategorydetails.h @@ -7,6 +7,9 @@ #include +#include "3rd-party/boolinq/boolinq.h" +#include "definitions/definitions.h" + namespace Ui { class FormCategoryDetails; } @@ -17,21 +20,29 @@ class FeedsModel; class RootItem; class QMenu; class QAction; +class MultiFeedEditCheckBox; class FormCategoryDetails : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit FormCategoryDetails(ServiceRoot* service_root, RootItem* parent_to_select = nullptr, QWidget* parent = nullptr); + explicit FormCategoryDetails(ServiceRoot* service_root, + RootItem* parent_to_select = nullptr, + QWidget* parent = nullptr); virtual ~FormCategoryDetails(); - template - T* addEditCategory(T* category_to_edit = nullptr); + template + QList addEditCategory(const QList& cats_to_edit = {}); - template + template T* category() const; + // Returns all cats. + template + QList categories() const; + protected: + bool isChangeAllowed(MultiFeedEditCheckBox* mcb) const; virtual void loadCategoryData(); protected slots: @@ -58,39 +69,52 @@ class FormCategoryDetails : public QDialog { private: QScopedPointer m_ui; - Category* m_category; + QList m_categories; ServiceRoot* m_serviceRoot; QMenu* m_iconMenu{}; QAction* m_actionLoadIconFromFile{}; QAction* m_actionUseDefaultIcon{}; RootItem* m_parentToSelect; bool m_creatingNew; + bool m_isBatchEdit; }; -template -inline T* FormCategoryDetails::addEditCategory(T* category_to_edit) { - m_creatingNew = category_to_edit == nullptr; +template +inline QList FormCategoryDetails::addEditCategory(const QList& cats_to_edit) { + m_creatingNew = cats_to_edit.isEmpty(); + m_isBatchEdit = cats_to_edit.size() > 1; if (m_creatingNew) { - m_category = new T(); + m_categories.append(new T()); } else { - m_category = category_to_edit; + m_categories.append(cats_to_edit); } loadCategoryData(); if (exec() == QDialog::DialogCode::Accepted) { - return category(); + return categories(); } else { - return nullptr; + return {}; } } -template +template inline T* FormCategoryDetails::category() const { - return qobject_cast(m_category); + return qobject_cast(m_categories.first()); +} + +template +inline QList FormCategoryDetails::categories() const { + std::list std_cats = boolinq::from(m_categories) + .select([](Category* fd) { + return qobject_cast(fd); + }) + .toStdList(); + + return FROM_STD_LIST(QList, std_cats); } #endif // FORMCATEGORYDETAILS_H diff --git a/src/librssguard/services/abstract/gui/formcategorydetails.ui b/src/librssguard/services/abstract/gui/formcategorydetails.ui index cf99b996c..1f544fe46 100644 --- a/src/librssguard/services/abstract/gui/formcategorydetails.ui +++ b/src/librssguard/services/abstract/gui/formcategorydetails.ui @@ -6,8 +6,8 @@ 0 0 - 397 - 209 + 455 + 262 @@ -19,13 +19,13 @@ - - - - - QFormLayout::AllNonFixedFieldsGrow - - + + + + + + + Parent folder @@ -35,20 +35,27 @@ - - - - Select parent item for your category. - - - - 13 - 12 - - - + + + + + + Select parent item for your category. + + + + 13 + 12 + + + + + + + + - + Title @@ -58,7 +65,17 @@ - + + + + + + + + + + + Description @@ -68,7 +85,17 @@ - + + + + + + + + + + + Icon @@ -78,52 +105,46 @@ - - - - - 0 - 0 - - - - - 40 - 40 - - - - Select icon for your category. - - - - - - - 20 - 20 - - - - QToolButton::InstantPopup - - - false - - - Qt::NoArrow - - - - - - - - - - + + + + + 0 + 0 + + + + + 40 + 40 + + + + Select icon for your category. + + + + + + + 20 + 20 + + + + QToolButton::InstantPopup + + + false + + + Qt::NoArrow + + + + Qt::Horizontal @@ -133,9 +154,27 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + + + MultiFeedEditCheckBox + QCheckBox +
multifeededitcheckbox.h
+
LineEditWithStatus QWidget @@ -157,8 +196,8 @@ reject() - 325 - 170 + 340 + 353 286 diff --git a/src/librssguard/services/abstract/gui/formfeeddetails.cpp b/src/librssguard/services/abstract/gui/formfeeddetails.cpp index fe691542a..19dafd509 100644 --- a/src/librssguard/services/abstract/gui/formfeeddetails.cpp +++ b/src/librssguard/services/abstract/gui/formfeeddetails.cpp @@ -8,6 +8,7 @@ #include "gui/guiutilities.h" #include "miscellaneous/iconfactory.h" #include "miscellaneous/textfactory.h" +#include "services/abstract/gui/multifeededitcheckbox.h" #include "services/abstract/rootitem.h" #include @@ -17,92 +18,150 @@ #include FormFeedDetails::FormFeedDetails(ServiceRoot* service_root, QWidget* parent) - : QDialog(parent), m_feed(nullptr), m_serviceRoot(service_root) { + : QDialog(parent), m_serviceRoot(service_root) { initialize(); createConnections(); } void FormFeedDetails::activateTab(int index) { - m_ui->m_tabWidget->setCurrentIndex(index); + m_ui.m_tabWidget->setCurrentIndex(index); } void FormFeedDetails::clearTabs() { - m_ui->m_tabWidget->clear(); + m_ui.m_tabWidget->clear(); } void FormFeedDetails::insertCustomTab(QWidget* custom_tab, const QString& title, int index) { - m_ui->m_tabWidget->insertTab(index, custom_tab, title); + m_ui.m_tabWidget->insertTab(index, custom_tab, title); } void FormFeedDetails::apply() { - // Setup common data for the feed. - m_feed->setAutoUpdateType(static_cast(m_ui->m_cmbAutoUpdateType - ->itemData(m_ui->m_cmbAutoUpdateType->currentIndex()) + QList fds = feeds(); + + for (Feed* fd : fds) { + // Setup common data for the feed. + if (isChangeAllowed(m_ui.m_mcbAutoDownloading)) { + fd->setAutoUpdateType(static_cast(m_ui.m_cmbAutoUpdateType + ->itemData(m_ui.m_cmbAutoUpdateType->currentIndex()) .toInt())); - m_feed->setAutoUpdateInterval(int(m_ui->m_spinAutoUpdateInterval->value())); - m_feed->setOpenArticlesDirectly(m_ui->m_cbOpenArticlesAutomatically->isChecked()); - m_feed->setIsRtl(m_ui->m_cbFeedRTL->isChecked()); - m_feed->setAddAnyDatetimeArticles(m_ui->m_cbAddAnyDateArticles->isChecked()); - m_feed->setDatetimeToAvoid(m_ui->m_gbAvoidOldArticles->isChecked() ? m_ui->m_dtDateTimeToAvoid->dateTime() - : TextFactory::parseDateTime(0)); - m_feed->setIsSwitchedOff(m_ui->m_cbDisableFeed->isChecked()); - m_feed->setIsQuiet(m_ui->m_cbSuppressFeed->isChecked()); + fd->setAutoUpdateInterval(int(m_ui.m_spinAutoUpdateInterval->value())); + } + + if (isChangeAllowed(m_ui.m_mcbOpenArticlesAutomatically)) { + fd->setOpenArticlesDirectly(m_ui.m_cbOpenArticlesAutomatically->isChecked()); + } + + if (isChangeAllowed(m_ui.m_mcbFeedRtl)) { + fd->setIsRtl(m_ui.m_cbFeedRTL->isChecked()); + } + + if (isChangeAllowed(m_ui.m_mcbAddAnyDateArticles)) { + fd->setAddAnyDatetimeArticles(m_ui.m_cbAddAnyDateArticles->isChecked()); + } + + if (isChangeAllowed(m_ui.m_mcbAvoidOldArticles)) { + fd->setDatetimeToAvoid(m_ui.m_gbAvoidOldArticles->isChecked() ? m_ui.m_dtDateTimeToAvoid->dateTime() + : TextFactory::parseDateTime(0)); + } + + if (isChangeAllowed(m_ui.m_mcbDisableFeed)) { + fd->setIsSwitchedOff(m_ui.m_cbDisableFeed->isChecked()); + } + + if (isChangeAllowed(m_ui.m_mcbSuppressFeed)) { + fd->setIsQuiet(m_ui.m_cbSuppressFeed->isChecked()); + } + + if (!m_creatingNew) { + // We need to make sure that common data are saved. + QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); + + DatabaseQueries::createOverwriteFeed(database, fd, m_serviceRoot->accountId(), fd->parent()->id()); + } + } if (!m_creatingNew) { - // We need to make sure that common data are saved. - QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); - - DatabaseQueries::createOverwriteFeed(database, m_feed, m_serviceRoot->accountId(), m_feed->parent()->id()); + m_serviceRoot->itemChanged(feeds()); } } +bool FormFeedDetails::isChangeAllowed(MultiFeedEditCheckBox* mcb) const { + return !m_isBatchEdit || mcb->isChecked(); +} + void FormFeedDetails::onAutoUpdateTypeChanged(int new_index) { Feed::AutoUpdateType auto_update_type = - static_cast(m_ui->m_cmbAutoUpdateType->itemData(new_index).toInt()); + static_cast(m_ui.m_cmbAutoUpdateType->itemData(new_index).toInt()); switch (auto_update_type) { case Feed::AutoUpdateType::DontAutoUpdate: case Feed::AutoUpdateType::DefaultAutoUpdate: - m_ui->m_spinAutoUpdateInterval->setEnabled(false); + m_ui.m_spinAutoUpdateInterval->setEnabled(false); break; default: - m_ui->m_spinAutoUpdateInterval->setEnabled(true); + m_ui.m_spinAutoUpdateInterval->setEnabled(true); } } void FormFeedDetails::createConnections() { - connect(m_ui->m_buttonBox, &QDialogButtonBox::accepted, this, &FormFeedDetails::acceptIfPossible); - connect(m_ui->m_cmbAutoUpdateType, + connect(m_ui.m_buttonBox, &QDialogButtonBox::accepted, this, &FormFeedDetails::acceptIfPossible); + connect(m_ui.m_cmbAutoUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &FormFeedDetails::onAutoUpdateTypeChanged); - connect(m_ui->m_cbAddAnyDateArticles, &QCheckBox::toggled, this, [this](bool checked) { - m_ui->m_gbAvoidOldArticles->setEnabled(!checked); + connect(m_ui.m_cbAddAnyDateArticles, &QCheckBox::toggled, this, [this](bool checked) { + m_ui.m_gbAvoidOldArticles->setEnabled(!checked); }); } void FormFeedDetails::loadFeedData() { + Feed* fd = feed(); + + if (m_isBatchEdit) { + // We hook batch selectors. + m_ui.m_mcbAutoDownloading->addActionWidget(m_ui.m_wdgAutoUpdate); + m_ui.m_mcbAddAnyDateArticles->addActionWidget(m_ui.m_cbAddAnyDateArticles); + m_ui.m_mcbOpenArticlesAutomatically->addActionWidget(m_ui.m_cbOpenArticlesAutomatically); + m_ui.m_mcbAvoidOldArticles->addActionWidget(m_ui.m_gbAvoidOldArticles); + m_ui.m_mcbDisableFeed->addActionWidget(m_ui.m_cbDisableFeed); + m_ui.m_mcbSuppressFeed->addActionWidget(m_ui.m_cbSuppressFeed); + m_ui.m_mcbFeedRtl->addActionWidget(m_ui.m_cbFeedRTL); + } + else { + // We hide batch selectors. + for (auto* cb : findChildren()) { + cb->hide(); + } + } + if (m_creatingNew) { GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("application-rss+xml")), tr("Add new feed")); } else { - GuiUtilities::applyDialogProperties(*this, m_feed->fullIcon(), tr("Edit \"%1\"").arg(m_feed->title())); + if (!m_isBatchEdit) { + GuiUtilities::applyDialogProperties(*this, fd->fullIcon(), tr("Edit \"%1\"").arg(fd->title())); + } + else { + GuiUtilities::applyDialogProperties(*this, + qApp->icons()->fromTheme(QSL("application-rss+xml")), + tr("Edit %n feeds", nullptr, m_feeds.size())); + } } - m_ui->m_cmbAutoUpdateType - ->setCurrentIndex(m_ui->m_cmbAutoUpdateType->findData(QVariant::fromValue(int(m_feed->autoUpdateType())))); - m_ui->m_spinAutoUpdateInterval->setValue(m_feed->autoUpdateInterval()); - m_ui->m_cbOpenArticlesAutomatically->setChecked(m_feed->openArticlesDirectly()); - m_ui->m_cbFeedRTL->setChecked(m_feed->isRtl()); - m_ui->m_cbAddAnyDateArticles->setChecked(m_feed->addAnyDatetimeArticles()); - m_ui->m_gbAvoidOldArticles->setChecked(m_feed->datetimeToAvoid().toMSecsSinceEpoch() > 0); - m_ui->m_dtDateTimeToAvoid->setDateTime(m_feed->datetimeToAvoid()); - m_ui->m_cbDisableFeed->setChecked(m_feed->isSwitchedOff()); - m_ui->m_cbSuppressFeed->setChecked(m_feed->isQuiet()); + m_ui.m_cmbAutoUpdateType + ->setCurrentIndex(m_ui.m_cmbAutoUpdateType->findData(QVariant::fromValue(int(fd->autoUpdateType())))); + m_ui.m_spinAutoUpdateInterval->setValue(fd->autoUpdateInterval()); + m_ui.m_cbOpenArticlesAutomatically->setChecked(fd->openArticlesDirectly()); + m_ui.m_cbFeedRTL->setChecked(fd->isRtl()); + m_ui.m_cbAddAnyDateArticles->setChecked(fd->addAnyDatetimeArticles()); + m_ui.m_gbAvoidOldArticles->setChecked(fd->datetimeToAvoid().toMSecsSinceEpoch() > 0); + m_ui.m_dtDateTimeToAvoid->setDateTime(fd->datetimeToAvoid()); + m_ui.m_cbDisableFeed->setChecked(fd->isSwitchedOff()); + m_ui.m_cbSuppressFeed->setChecked(fd->isQuiet()); } void FormFeedDetails::acceptIfPossible() { @@ -122,19 +181,18 @@ void FormFeedDetails::acceptIfPossible() { } void FormFeedDetails::initialize() { - m_ui.reset(new Ui::FormFeedDetails()); - m_ui->setupUi(this); + m_ui.setupUi(this); - m_ui->m_dtDateTimeToAvoid + m_ui.m_dtDateTimeToAvoid ->setDisplayFormat(qApp->localization()->loadedLocale().dateTimeFormat(QLocale::FormatType::ShortFormat)); // Setup auto-update options. - m_ui->m_spinAutoUpdateInterval->setMode(TimeSpinBox::Mode::MinutesSeconds); - m_ui->m_spinAutoUpdateInterval->setValue(DEFAULT_AUTO_UPDATE_INTERVAL); - m_ui->m_cmbAutoUpdateType->addItem(tr("Fetch articles using global interval"), - QVariant::fromValue(int(Feed::AutoUpdateType::DefaultAutoUpdate))); - m_ui->m_cmbAutoUpdateType->addItem(tr("Fetch articles every"), - QVariant::fromValue(int(Feed::AutoUpdateType::SpecificAutoUpdate))); - m_ui->m_cmbAutoUpdateType->addItem(tr("Disable auto-fetching of articles"), - QVariant::fromValue(int(Feed::AutoUpdateType::DontAutoUpdate))); + m_ui.m_spinAutoUpdateInterval->setMode(TimeSpinBox::Mode::MinutesSeconds); + m_ui.m_spinAutoUpdateInterval->setValue(DEFAULT_AUTO_UPDATE_INTERVAL); + m_ui.m_cmbAutoUpdateType->addItem(tr("Fetch articles using global interval"), + QVariant::fromValue(int(Feed::AutoUpdateType::DefaultAutoUpdate))); + m_ui.m_cmbAutoUpdateType->addItem(tr("Fetch articles every"), + QVariant::fromValue(int(Feed::AutoUpdateType::SpecificAutoUpdate))); + m_ui.m_cmbAutoUpdateType->addItem(tr("Disable auto-fetching of articles"), + QVariant::fromValue(int(Feed::AutoUpdateType::DontAutoUpdate))); } diff --git a/src/librssguard/services/abstract/gui/formfeeddetails.h b/src/librssguard/services/abstract/gui/formfeeddetails.h index f603810bc..7e08feca8 100644 --- a/src/librssguard/services/abstract/gui/formfeeddetails.h +++ b/src/librssguard/services/abstract/gui/formfeeddetails.h @@ -7,6 +7,9 @@ #include "ui_formfeeddetails.h" +#include "3rd-party/boolinq/boolinq.h" +#include "definitions/definitions.h" + namespace Ui { class FormFeedDetails; } @@ -17,18 +20,23 @@ class Category; class RootItem; class FormFeedDetails : public QDialog { - Q_OBJECT + Q_OBJECT public: explicit FormFeedDetails(ServiceRoot* service_root, QWidget* parent = nullptr); virtual ~FormFeedDetails() = default; - template - T* addEditFeed(T* feed_to_edit = nullptr); + template + QList addEditFeed(const QList& feeds_to_edit = {}); - template + // Returns first feed. + template T* feed() const; + // Returns all feeds. + template + QList feeds() const; + protected slots: void activateTab(int index); void clearTabs(); @@ -39,6 +47,7 @@ class FormFeedDetails : public QDialog { virtual void apply(); protected: + bool isChangeAllowed(MultiFeedEditCheckBox* mcb) const; void insertCustomTab(QWidget* custom_tab, const QString& title, int index); // Sets the feed which will be edited. @@ -55,37 +64,49 @@ class FormFeedDetails : public QDialog { void initialize(); protected: - QScopedPointer m_ui; - Feed* m_feed; + Ui::FormFeedDetails m_ui; + QList m_feeds; ServiceRoot* m_serviceRoot; bool m_creatingNew; + bool m_isBatchEdit; }; -template -inline T* FormFeedDetails::addEditFeed(T* feed_to_edit) { - m_creatingNew = feed_to_edit == nullptr; +template +inline QList FormFeedDetails::addEditFeed(const QList& feeds_to_edit) { + m_creatingNew = feeds_to_edit.isEmpty(); + m_isBatchEdit = feeds_to_edit.size() > 1; if (m_creatingNew) { - m_feed = new T(); + m_feeds.append(new T()); } else { - m_feed = feed_to_edit; + m_feeds.append(feeds_to_edit); } - // Load custom logic for feed data loading. loadFeedData(); if (exec() == QDialog::DialogCode::Accepted) { - return feed(); + return feeds(); } else { - return nullptr; + return {}; } } -template +template inline T* FormFeedDetails::feed() const { - return qobject_cast(m_feed); + return qobject_cast(m_feeds.first()); +} + +template +inline QList FormFeedDetails::feeds() const { + std::list std_fds = boolinq::from(m_feeds) + .select([](Feed* fd) { + return qobject_cast(fd); + }) + .toStdList(); + + return FROM_STD_LIST(QList, std_fds); } #endif // FORMFEEDDETAILS_H diff --git a/src/librssguard/services/abstract/gui/formfeeddetails.ui b/src/librssguard/services/abstract/gui/formfeeddetails.ui index 5b77aced4..fd5538426 100644 --- a/src/librssguard/services/abstract/gui/formfeeddetails.ui +++ b/src/librssguard/services/abstract/gui/formfeeddetails.ui @@ -24,92 +24,140 @@ Articles - - - - Auto-downloading of articles - - - m_cmbAutoUpdateType - - - - - + + - - - Select the auto-download strategy for messages of this feed. Default auto-download strategy means that new messges of this feed will be downloaded in time intervals set in application settings. - - + - - - false - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Auto-downloading of articles + + + m_cmbAutoUpdateType + + + + + + + Select the auto-download strategy for messages of this feed. Default auto-download strategy means that new messges of this feed will be downloaded in time intervals set in application settings. + + + + + + + false + + + + - - - Open articles via their URL automatically - - + + + + + + + + Open articles via their URL automatically + + + + + + + 150 + 0 + + Qt::Horizontal - - - Add articles with any date into the database - - + + + + + + + + Add articles with any date into the database + + + + - - - true - - - Avoid adding articles before this date into the database - - - true - - - false - - - - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + + + + + + + + true + + + Avoid adding articles before this date into the database + + + true + + + false + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + @@ -118,26 +166,47 @@ Miscellaneous - - - - Ignore notifications for this feed - - + + + + + + + + + Ignore notifications for this feed + + + + - - - - Disable this feed - - + + + + + + + + + Disable this feed + + + + - - - - Right-to-left layout - - + + + + + + + + + Right-to-left layout + + + + @@ -164,11 +233,13 @@ QDoubleSpinBox
timespinbox.h
+ + MultiFeedEditCheckBox + QCheckBox +
multifeededitcheckbox.h
+
- m_tabWidget - m_cmbAutoUpdateType - m_spinAutoUpdateInterval m_cbOpenArticlesAutomatically m_cbAddAnyDateArticles m_gbAvoidOldArticles diff --git a/src/librssguard/services/abstract/gui/multifeededitcheckbox.cpp b/src/librssguard/services/abstract/gui/multifeededitcheckbox.cpp new file mode 100644 index 000000000..388c616a1 --- /dev/null +++ b/src/librssguard/services/abstract/gui/multifeededitcheckbox.cpp @@ -0,0 +1,22 @@ +// For license of this file, see /LICENSE.md. + +#include "services/abstract/gui/multifeededitcheckbox.h" + +MultiFeedEditCheckBox::MultiFeedEditCheckBox(QWidget* parent) : QCheckBox(parent) { + setToolTip(tr("Apply this to all edited feeds.")); + setText(QString(4, ' ')); + setSizePolicy(QSizePolicy::Policy::Maximum, QSizePolicy::Policy::Maximum); +} + +QList MultiFeedEditCheckBox::actionWidgets() const { + return m_actionWidgets; +} + +void MultiFeedEditCheckBox::addActionWidget(QWidget* widget) { + if (widget != nullptr) { + m_actionWidgets.append(widget); + connect(this, &MultiFeedEditCheckBox::toggled, widget, &QWidget::setEnabled); + + emit toggled(isChecked()); + } +} diff --git a/src/librssguard/services/abstract/gui/multifeededitcheckbox.h b/src/librssguard/services/abstract/gui/multifeededitcheckbox.h new file mode 100644 index 000000000..835ecde3b --- /dev/null +++ b/src/librssguard/services/abstract/gui/multifeededitcheckbox.h @@ -0,0 +1,21 @@ +// For license of this file, see /LICENSE.md. + +#ifndef MULTIFEEDEDITCHECKBOX_H +#define MULTIFEEDEDITCHECKBOX_H + +#include + +class MultiFeedEditCheckBox : public QCheckBox { + Q_OBJECT + + public: + explicit MultiFeedEditCheckBox(QWidget* parent = nullptr); + + QList actionWidgets() const; + void addActionWidget(QWidget* widget); + + private: + QList m_actionWidgets; +}; + +#endif // MULTIFEEDEDITCHECKBOX_H diff --git a/src/librssguard/services/abstract/label.cpp b/src/librssguard/services/abstract/label.cpp index 8791a4845..24dfa8823 100644 --- a/src/librssguard/services/abstract/label.cpp +++ b/src/librssguard/services/abstract/label.cpp @@ -43,19 +43,6 @@ bool Label::canBeEdited() const { return Globals::hasFlag(getParentServiceRoot()->supportedLabelOperations(), ServiceRoot::LabelOperation::Editing); } -bool Label::editViaGui() { - FormAddEditLabel form(qApp->mainFormWidget()); - - if (form.execForEdit(this)) { - QSqlDatabase db = qApp->database()->driver()->connection(metaObject()->className()); - - return DatabaseQueries::updateLabel(db, this); - } - else { - return false; - } -} - bool Label::canBeDeleted() const { return Globals::hasFlag(getParentServiceRoot()->supportedLabelOperations(), ServiceRoot::LabelOperation::Deleting); } diff --git a/src/librssguard/services/abstract/label.h b/src/librssguard/services/abstract/label.h index d6358087b..bf32acdf8 100644 --- a/src/librssguard/services/abstract/label.h +++ b/src/librssguard/services/abstract/label.h @@ -28,7 +28,6 @@ class RSSGUARD_DLLSPEC Label : public RootItem { virtual int countOfAllMessages() const; virtual int countOfUnreadMessages() const; virtual bool canBeEdited() const; - virtual bool editViaGui(); virtual bool canBeDeleted() const; virtual bool deleteViaGui(); virtual void updateCounts(bool including_total_count); diff --git a/src/librssguard/services/abstract/rootitem.cpp b/src/librssguard/services/abstract/rootitem.cpp index d1a836ad0..db48bcecd 100644 --- a/src/librssguard/services/abstract/rootitem.cpp +++ b/src/librssguard/services/abstract/rootitem.cpp @@ -59,10 +59,6 @@ bool RootItem::canBeEdited() const { return false; } -bool RootItem::editViaGui() { - return false; -} - bool RootItem::canBeDeleted() const { return false; } diff --git a/src/librssguard/services/abstract/rootitem.h b/src/librssguard/services/abstract/rootitem.h index 748519d13..182d34eda 100644 --- a/src/librssguard/services/abstract/rootitem.h +++ b/src/librssguard/services/abstract/rootitem.h @@ -28,11 +28,19 @@ class RSSGUARD_DLLSPEC RootItem : public QObject { Q_PROPERTY(QString customId READ customId) public: - enum class ReadStatus { Unread = 0, Read = 1, Unknown = 256 }; + enum class ReadStatus { + Unread = 0, + Read = 1, + Unknown = 256 + }; // Holds statuses for messages // to be switched importance (starred). - enum class Importance { NotImportant = 0, Important = 1, Unknown = 256 }; + enum class Importance { + NotImportant = 0, + Important = 1, + Unknown = 256 + }; // Describes the kind of the item. enum class Kind { @@ -66,10 +74,6 @@ class RSSGUARD_DLLSPEC RootItem : public QObject { // Can properties of this item be edited? virtual bool canBeEdited() const; - // Performs editing of properties of this item (probably via dialog) - // and returns result status. - virtual bool editViaGui(); - // Can the item be deleted? virtual bool canBeDeleted() const; diff --git a/src/librssguard/services/abstract/search.cpp b/src/librssguard/services/abstract/search.cpp index a5141229b..342ec6390 100644 --- a/src/librssguard/services/abstract/search.cpp +++ b/src/librssguard/services/abstract/search.cpp @@ -44,28 +44,6 @@ bool Search::canBeEdited() const { return true; } -bool Search::editViaGui() { - FormAddEditProbe form(qApp->mainFormWidget()); - - if (form.execForEdit(this)) { - QSqlDatabase db = qApp->database()->driver()->connection(metaObject()->className()); - - try { - DatabaseQueries::updateProbe(db, this); - updateCounts(true); - getParentServiceRoot()->itemChanged({this}); - return true; - } - catch (const ApplicationException& ex) { - qCriticalNN << LOGSEC_CORE << "Failed to edit probe:" << QUOTE_W_SPACE_DOT(ex.message()); - return false; - } - } - else { - return true; - } -} - bool Search::canBeDeleted() const { return true; } diff --git a/src/librssguard/services/abstract/search.h b/src/librssguard/services/abstract/search.h index e9a89898f..56b0e7fe4 100644 --- a/src/librssguard/services/abstract/search.h +++ b/src/librssguard/services/abstract/search.h @@ -32,7 +32,6 @@ class RSSGUARD_DLLSPEC Search : public RootItem { virtual int countOfAllMessages() const; virtual int countOfUnreadMessages() const; virtual bool canBeEdited() const; - virtual bool editViaGui(); virtual bool canBeDeleted() const; virtual bool deleteViaGui(); virtual void updateCounts(bool including_total_count); diff --git a/src/librssguard/services/abstract/serviceroot.cpp b/src/librssguard/services/abstract/serviceroot.cpp index b2a8f2293..8e6344ab2 100644 --- a/src/librssguard/services/abstract/serviceroot.cpp +++ b/src/librssguard/services/abstract/serviceroot.cpp @@ -14,6 +14,11 @@ #include "services/abstract/category.h" #include "services/abstract/feed.h" #include "services/abstract/gui/custommessagepreviewer.h" +#include "services/abstract/gui/formaccountdetails.h" +#include "services/abstract/gui/formaddeditlabel.h" +#include "services/abstract/gui/formaddeditprobe.h" +#include "services/abstract/gui/formcategorydetails.h" +#include "services/abstract/gui/formfeeddetails.h" #include "services/abstract/importantnode.h" #include "services/abstract/labelsnode.h" #include "services/abstract/recyclebin.h" @@ -44,6 +49,95 @@ bool ServiceRoot::deleteViaGui() { } } +void ServiceRoot::editItemsViaGui(const QList& items) { + // Feed editing. + auto std_feeds = boolinq::from(items) + .select([](RootItem* it) { + return qobject_cast(it); + }) + .where([](Feed* fd) { + return fd != nullptr; + }) + .toStdList(); + + if (!std_feeds.empty()) { + QScopedPointer form_pointer(new FormFeedDetails(this, qApp->mainFormWidget())); + + form_pointer->addEditFeed(FROM_STD_LIST(QList, std_feeds)); + return; + } + + // Category editing. + auto std_categories = boolinq::from(items) + .select([](RootItem* it) { + return qobject_cast(it); + }) + .where([](Category* fd) { + return fd != nullptr; + }) + .toStdList(); + + if (!std_categories.empty()) { + QScopedPointer form_pointer(new FormCategoryDetails(this, nullptr, qApp->mainFormWidget())); + + form_pointer->addEditCategory(FROM_STD_LIST(QList, std_categories)); + return; + } + + // Label editing. + auto std_labels = boolinq::from(items) + .select([](RootItem* it) { + return qobject_cast(it); + }) + .where([](Label* fd) { + return fd != nullptr; + }) + .toStdList(); + + if (std_labels.size() == 1) { + // Support editing labels one by one. + FormAddEditLabel form(qApp->mainFormWidget()); + Label* lbl = std_labels.front(); + + if (form.execForEdit(lbl)) { + QSqlDatabase db = qApp->database()->driver()->connection(metaObject()->className()); + + DatabaseQueries::updateLabel(db, lbl); + } + + return; + } + + // Probe editing. + auto std_probes = boolinq::from(items) + .select([](RootItem* it) { + return qobject_cast(it); + }) + .where([](Search* fd) { + return fd != nullptr; + }) + .toStdList(); + + if (std_probes.size() == 1) { + // Support editing probes one by one. + FormAddEditProbe form(qApp->mainFormWidget()); + Search* probe = std_probes.front(); + + if (form.execForEdit(probe)) { + QSqlDatabase db = qApp->database()->driver()->connection(metaObject()->className()); + + DatabaseQueries::updateProbe(db, probe); + updateCounts(probe); + itemChanged({probe}); + } + + return; + } + + qApp->showGuiMessage(Notification::Event::GeneralEvent, + {tr("Unsupported"), tr("This is not suppported (yet)."), QSystemTrayIcon::MessageIcon::Warning}); +} + bool ServiceRoot::markAsReadUnread(RootItem::ReadStatus status) { auto* cache = dynamic_cast(this); @@ -356,6 +450,12 @@ void ServiceRoot::requestItemReassignment(RootItem* item, RootItem* new_parent) emit itemReassignmentRequested(item, new_parent); } +void ServiceRoot::requestItemsReassignment(const QList& items, RootItem* new_parent) { + for (RootItem* it : items) { + requestItemReassignment(it, new_parent); + } +} + void ServiceRoot::requestItemRemoval(RootItem* item) { emit itemRemovalRequested(item); } @@ -479,6 +579,10 @@ UnreadNode* ServiceRoot::unreadNode() const { return m_unreadNode; } +FormAccountDetails* ServiceRoot::accountSetupDialog() const { + return nullptr; +} + void ServiceRoot::onDatabaseCleanup() {} void ServiceRoot::syncIn() { diff --git a/src/librssguard/services/abstract/serviceroot.h b/src/librssguard/services/abstract/serviceroot.h index 0aeac09f1..94ab55252 100644 --- a/src/librssguard/services/abstract/serviceroot.h +++ b/src/librssguard/services/abstract/serviceroot.h @@ -24,6 +24,7 @@ class Label; class MessagesModel; class CustomMessagePreviewer; class CacheForServiceRoot; +class FormAccountDetails; // THIS IS the root node of the service. // NOTE: The root usually contains some core functionality of the @@ -59,10 +60,12 @@ class ServiceRoot : public RootItem { SearchsNode* probesNode() const; UnreadNode* unreadNode() const; + virtual FormAccountDetails* accountSetupDialog() const; virtual void onDatabaseCleanup(); virtual void updateCounts(bool including_total_count); virtual bool canBeDeleted() const; virtual bool deleteViaGui(); + virtual void editItemsViaGui(const QList& items); virtual bool markAsReadUnread(ReadStatus status); virtual QList undeletedMessages() const; virtual bool supportsFeedAdding() const; @@ -222,6 +225,7 @@ class ServiceRoot : public RootItem { void requestItemExpand(const QList& items, bool expand); void requestItemExpandStateSave(RootItem* subtree_root); void requestItemReassignment(RootItem* item, RootItem* new_parent); + void requestItemsReassignment(const QList& items, RootItem* new_parent); void requestItemRemoval(RootItem* item); // Some message/feed attribute selectors. diff --git a/src/librssguard/services/feedly/feedlyserviceroot.cpp b/src/librssguard/services/feedly/feedlyserviceroot.cpp index c72e59bec..7e76075c5 100644 --- a/src/librssguard/services/feedly/feedlyserviceroot.cpp +++ b/src/librssguard/services/feedly/feedlyserviceroot.cpp @@ -32,11 +32,19 @@ bool FeedlyServiceRoot::canBeEdited() const { return true; } -bool FeedlyServiceRoot::editViaGui() { - FormEditFeedlyAccount form_pointer(qApp->mainFormWidget()); +FormAccountDetails* FeedlyServiceRoot::accountSetupDialog() const { + return new FormEditFeedlyAccount(qApp->mainFormWidget()); +} - form_pointer.addEditAccount(this); - return true; +void FeedlyServiceRoot::editItemsViaGui(const QList& items) { + if (items.first()->kind() == RootItem::Kind::ServiceRoot) { + QScopedPointer p(qobject_cast(accountSetupDialog())); + + p->addEditAccount(this); + return; + } + + ServiceRoot::editItemsViaGui(items); } QVariantHash FeedlyServiceRoot::customDatabaseData() const { diff --git a/src/librssguard/services/feedly/feedlyserviceroot.h b/src/librssguard/services/feedly/feedlyserviceroot.h index 0f432a032..f01ff029c 100644 --- a/src/librssguard/services/feedly/feedlyserviceroot.h +++ b/src/librssguard/services/feedly/feedlyserviceroot.h @@ -9,14 +9,15 @@ class FeedlyNetwork; class FeedlyServiceRoot : public ServiceRoot, public CacheForServiceRoot { - Q_OBJECT + Q_OBJECT public: explicit FeedlyServiceRoot(RootItem* parent = nullptr); virtual bool isSyncable() const; virtual bool canBeEdited() const; - virtual bool editViaGui(); + virtual void editItemsViaGui(const QList& items); + virtual FormAccountDetails* accountSetupDialog() const; virtual void start(bool freshly_activated); virtual QString code() const; virtual void saveAllCachedData(bool ignore_errors); diff --git a/src/librssguard/services/gmail/gmailserviceroot.cpp b/src/librssguard/services/gmail/gmailserviceroot.cpp index 944fbbcf7..88ceb574b 100644 --- a/src/librssguard/services/gmail/gmailserviceroot.cpp +++ b/src/librssguard/services/gmail/gmailserviceroot.cpp @@ -162,11 +162,19 @@ bool GmailServiceRoot::canBeEdited() const { return true; } -bool GmailServiceRoot::editViaGui() { - FormEditGmailAccount form_pointer(qApp->mainFormWidget()); +FormAccountDetails* GmailServiceRoot::accountSetupDialog() const { + return new FormEditGmailAccount(qApp->mainFormWidget()); +} - form_pointer.addEditAccount(this); - return true; +void GmailServiceRoot::editItemsViaGui(const QList& items) { + if (items.first()->kind() == RootItem::Kind::ServiceRoot) { + QScopedPointer p(qobject_cast(accountSetupDialog())); + + p->addEditAccount(this); + return; + } + + ServiceRoot::editItemsViaGui(items); } bool GmailServiceRoot::supportsFeedAdding() const { diff --git a/src/librssguard/services/gmail/gmailserviceroot.h b/src/librssguard/services/gmail/gmailserviceroot.h index 1ae5adf01..a299033b6 100644 --- a/src/librssguard/services/gmail/gmailserviceroot.h +++ b/src/librssguard/services/gmail/gmailserviceroot.h @@ -24,7 +24,8 @@ class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot { virtual QList serviceMenu(); virtual bool isSyncable() const; virtual bool canBeEdited() const; - virtual bool editViaGui(); + virtual void editItemsViaGui(const QList& items); + virtual FormAccountDetails* accountSetupDialog() const; virtual bool supportsFeedAdding() const; virtual bool supportsCategoryAdding() const; virtual void start(bool freshly_activated); diff --git a/src/librssguard/services/greader/greaderserviceroot.cpp b/src/librssguard/services/greader/greaderserviceroot.cpp index 4265be23b..a1279e7de 100644 --- a/src/librssguard/services/greader/greaderserviceroot.cpp +++ b/src/librssguard/services/greader/greaderserviceroot.cpp @@ -26,11 +26,19 @@ bool GreaderServiceRoot::canBeEdited() const { return true; } -bool GreaderServiceRoot::editViaGui() { - FormEditGreaderAccount form_pointer(qApp->mainFormWidget()); +FormAccountDetails* GreaderServiceRoot::accountSetupDialog() const { + return new FormEditGreaderAccount(qApp->mainFormWidget()); +} - form_pointer.addEditAccount(this); - return true; +void GreaderServiceRoot::editItemsViaGui(const QList& items) { + if (items.first()->kind() == RootItem::Kind::ServiceRoot) { + QScopedPointer p(qobject_cast(accountSetupDialog())); + + p->addEditAccount(this); + return; + } + + ServiceRoot::editItemsViaGui(items); } QVariantHash GreaderServiceRoot::customDatabaseData() const { diff --git a/src/librssguard/services/greader/greaderserviceroot.h b/src/librssguard/services/greader/greaderserviceroot.h index b045d7b14..7f627f43e 100644 --- a/src/librssguard/services/greader/greaderserviceroot.h +++ b/src/librssguard/services/greader/greaderserviceroot.h @@ -28,7 +28,8 @@ class GreaderServiceRoot : public ServiceRoot, public CacheForServiceRoot { virtual bool isSyncable() const; virtual bool canBeEdited() const; - virtual bool editViaGui(); + virtual void editItemsViaGui(const QList& items); + virtual FormAccountDetails* accountSetupDialog() const; virtual void start(bool freshly_activated); virtual QString code() const; virtual void saveAllCachedData(bool ignore_errors); diff --git a/src/librssguard/services/owncloud/owncloudserviceroot.cpp b/src/librssguard/services/owncloud/owncloudserviceroot.cpp index 03a429b8b..b44998a7b 100644 --- a/src/librssguard/services/owncloud/owncloudserviceroot.cpp +++ b/src/librssguard/services/owncloud/owncloudserviceroot.cpp @@ -30,11 +30,19 @@ bool OwnCloudServiceRoot::canBeEdited() const { return true; } -bool OwnCloudServiceRoot::editViaGui() { - QScopedPointer form_pointer(new FormEditOwnCloudAccount(qApp->mainFormWidget())); +FormAccountDetails* OwnCloudServiceRoot::accountSetupDialog() const { + return new FormEditOwnCloudAccount(qApp->mainFormWidget()); +} - form_pointer->addEditAccount(this); - return true; +void OwnCloudServiceRoot::editItemsViaGui(const QList& items) { + if (items.first()->kind() == RootItem::Kind::ServiceRoot) { + QScopedPointer p(qobject_cast(accountSetupDialog())); + + p->addEditAccount(this); + return; + } + + ServiceRoot::editItemsViaGui(items); } bool OwnCloudServiceRoot::supportsFeedAdding() const { diff --git a/src/librssguard/services/owncloud/owncloudserviceroot.h b/src/librssguard/services/owncloud/owncloudserviceroot.h index 9f3838c55..69bc5e94b 100644 --- a/src/librssguard/services/owncloud/owncloudserviceroot.h +++ b/src/librssguard/services/owncloud/owncloudserviceroot.h @@ -12,7 +12,7 @@ class OwnCloudNetworkFactory; class Mutex; class OwnCloudServiceRoot : public ServiceRoot, public CacheForServiceRoot { - Q_OBJECT + Q_OBJECT public: explicit OwnCloudServiceRoot(RootItem* parent = nullptr); @@ -20,7 +20,8 @@ class OwnCloudServiceRoot : public ServiceRoot, public CacheForServiceRoot { virtual bool isSyncable() const; virtual bool canBeEdited() const; - virtual bool editViaGui(); + virtual void editItemsViaGui(const QList& items); + virtual FormAccountDetails* accountSetupDialog() const; virtual bool supportsFeedAdding() const; virtual bool supportsCategoryAdding() const; virtual void start(bool freshly_activated); diff --git a/src/librssguard/services/reddit/redditserviceroot.cpp b/src/librssguard/services/reddit/redditserviceroot.cpp index 0e11fa555..20c50ecbd 100644 --- a/src/librssguard/services/reddit/redditserviceroot.cpp +++ b/src/librssguard/services/reddit/redditserviceroot.cpp @@ -80,11 +80,19 @@ bool RedditServiceRoot::canBeEdited() const { return true; } -bool RedditServiceRoot::editViaGui() { - FormEditRedditAccount form_pointer(qApp->mainFormWidget()); +void RedditServiceRoot::editItemsViaGui(const QList& items) { + if (items.first()->kind() == RootItem::Kind::ServiceRoot) { + QScopedPointer p(qobject_cast(accountSetupDialog())); - form_pointer.addEditAccount(this); - return true; + p->addEditAccount(this); + return; + } + + ServiceRoot::editItemsViaGui(items); +} + +FormAccountDetails* RedditServiceRoot::accountSetupDialog() const { + return new FormEditRedditAccount(qApp->mainFormWidget()); } bool RedditServiceRoot::supportsFeedAdding() const { diff --git a/src/librssguard/services/reddit/redditserviceroot.h b/src/librssguard/services/reddit/redditserviceroot.h index 5600e0a95..d8c0d217e 100644 --- a/src/librssguard/services/reddit/redditserviceroot.h +++ b/src/librssguard/services/reddit/redditserviceroot.h @@ -9,7 +9,7 @@ class RedditNetworkFactory; class RedditServiceRoot : public ServiceRoot, public CacheForServiceRoot { - Q_OBJECT + Q_OBJECT public: explicit RedditServiceRoot(RootItem* parent = nullptr); @@ -19,7 +19,8 @@ class RedditServiceRoot : public ServiceRoot, public CacheForServiceRoot { virtual bool isSyncable() const; virtual bool canBeEdited() const; - virtual bool editViaGui(); + virtual void editItemsViaGui(const QList& items); + virtual FormAccountDetails* accountSetupDialog() const; virtual bool supportsFeedAdding() const; virtual bool supportsCategoryAdding() const; virtual void start(bool freshly_activated); diff --git a/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp b/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp index ece69d1e2..f86915b08 100644 --- a/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp +++ b/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp @@ -116,7 +116,10 @@ void FormDiscoverFeeds::onDiscoveryFinished() { loadDiscoveredFeeds(res); } catch (const ApplicationException& ex) { - // TODO: display error + qApp->showGuiMessage(Notification::Event::GeneralEvent, + {tr("Cannot discover feeds"), + tr("Error: %1").arg(ex.message()), + QSystemTrayIcon::MessageIcon::Critical}); } setEnabled(true); @@ -212,7 +215,7 @@ void FormDiscoverFeeds::addSingleFeed() { fd->source(), qApp->mainFormWidget())); - if (form_pointer->addEditFeed() != nullptr) { + if (!form_pointer->addEditFeed().isEmpty()) { // Feed was added, remove from list. if (m_discoveredModel->removeItem(idx) != nullptr) { // Feed was guessed by the dialog, we do not need this object. diff --git a/src/librssguard/services/standard/gui/formeditstandardaccount.h b/src/librssguard/services/standard/gui/formeditstandardaccount.h index 354231d35..6b5081051 100644 --- a/src/librssguard/services/standard/gui/formeditstandardaccount.h +++ b/src/librssguard/services/standard/gui/formeditstandardaccount.h @@ -6,6 +6,8 @@ #include "services/abstract/gui/formaccountdetails.h" class FormEditStandardAccount : public FormAccountDetails { + Q_OBJECT + public: explicit FormEditStandardAccount(QWidget* parent = nullptr); diff --git a/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp b/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp index 45e97d833..40f0e6b13 100644 --- a/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp +++ b/src/librssguard/services/standard/gui/formstandardfeeddetails.cpp @@ -60,54 +60,119 @@ void FormStandardFeedDetails::guessIconOnly() { } void FormStandardFeedDetails::onTitleChanged(const QString& title) { - m_ui->m_buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(!title.simplified().isEmpty()); + m_ui.m_buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(!title.simplified().isEmpty()); } void FormStandardFeedDetails::apply() { FormFeedDetails::apply(); - auto* std_feed = feed(); + QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); RootItem* parent = m_standardFeedDetails->m_ui.m_cmbParentCategory->currentData().value(); - StandardFeed::Type type = static_cast(m_standardFeedDetails->m_ui.m_cmbType ->itemData(m_standardFeedDetails->m_ui.m_cmbType->currentIndex()) .toInt()); - // Setup data for new_feed. - std_feed->setTitle(m_standardFeedDetails->m_ui.m_txtTitle->lineEdit()->text().simplified()); - std_feed->setCreationDate(QDateTime::currentDateTime()); - std_feed->setDescription(m_standardFeedDetails->m_ui.m_txtDescription->lineEdit()->text()); - std_feed->setIcon(m_standardFeedDetails->m_ui.m_btnIcon->icon()); + QList fds = feeds(); - std_feed->setSource(m_standardFeedDetails->m_ui.m_txtSource->textEdit()->toPlainText()); - std_feed->setLastEtag({}); + for (StandardFeed* std_feed : fds) { + // Setup data for the feed. + if (isChangeAllowed(m_standardFeedDetails->m_ui.m_mcbTitle)) { + std_feed->setTitle(m_standardFeedDetails->m_ui.m_txtTitle->lineEdit()->text().simplified()); + } - std_feed->setEncoding(m_standardFeedDetails->m_ui.m_cmbEncoding->currentText()); - std_feed->setType(type); - std_feed->setSourceType(m_standardFeedDetails->sourceType()); - std_feed->setPostProcessScript(m_standardFeedDetails->m_ui.m_txtPostProcessScript->textEdit()->toPlainText()); + if (isChangeAllowed(m_standardFeedDetails->m_ui.m_mcbDescription)) { + std_feed->setDescription(m_standardFeedDetails->m_ui.m_txtDescription->lineEdit()->text()); + } - std_feed->setProtection(m_authDetails->authenticationType()); - std_feed->setUsername(m_authDetails->m_txtUsername->lineEdit()->text()); - std_feed->setPassword(m_authDetails->m_txtPassword->lineEdit()->text()); + if (isChangeAllowed(m_standardFeedDetails->m_ui.m_mcbIcon)) { + std_feed->setIcon(m_standardFeedDetails->m_ui.m_btnIcon->icon()); + } - QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); + if (isChangeAllowed(m_standardFeedDetails->m_ui.m_mcbSource)) { + std_feed->setSource(m_standardFeedDetails->m_ui.m_txtSource->textEdit()->toPlainText()); + } - try { - DatabaseQueries::createOverwriteFeed(database, std_feed, m_serviceRoot->accountId(), parent->id()); - } - catch (const ApplicationException& ex) { - qFatal("Cannot save feed: '%s'.", qPrintable(ex.message())); + if (isChangeAllowed(m_standardFeedDetails->m_ui.m_mcbSourceType)) { + std_feed->setSourceType(m_standardFeedDetails->sourceType()); + } + + if (isChangeAllowed(m_standardFeedDetails->m_ui.m_mcbType)) { + std_feed->setType(type); + } + + if (isChangeAllowed(m_standardFeedDetails->m_ui.m_mcbEncoding)) { + std_feed->setEncoding(m_standardFeedDetails->m_ui.m_cmbEncoding->currentText()); + } + + if (isChangeAllowed(m_standardFeedDetails->m_ui.m_mcbPostProcessScript)) { + std_feed->setPostProcessScript(m_standardFeedDetails->m_ui.m_txtPostProcessScript->textEdit()->toPlainText()); + } + + if (isChangeAllowed(m_authDetails->m_mcbAuthType)) { + std_feed->setProtection(m_authDetails->authenticationType()); + } + + if (isChangeAllowed(m_authDetails->m_mcbAuthentication)) { + std_feed->setUsername(m_authDetails->m_txtUsername->lineEdit()->text()); + std_feed->setPassword(m_authDetails->m_txtPassword->lineEdit()->text()); + } + + std_feed->setCreationDate(QDateTime::currentDateTime()); + std_feed->setLastEtag({}); + + int new_parent_id; + + if (isChangeAllowed(m_standardFeedDetails->m_ui.m_mcbParentCategory)) { + new_parent_id = parent->id(); + } + else { + new_parent_id = std_feed->parent()->id(); + } + + try { + DatabaseQueries::createOverwriteFeed(database, std_feed, m_serviceRoot->accountId(), new_parent_id); + } + catch (const ApplicationException& ex) { + qFatal("Cannot save feed: '%s'.", qPrintable(ex.message())); + } + + if (isChangeAllowed(m_standardFeedDetails->m_ui.m_mcbParentCategory)) { + m_serviceRoot->requestItemReassignment(std_feed, parent); + } } - m_serviceRoot->requestItemReassignment(m_feed, parent); - m_serviceRoot->itemChanged({m_feed}); + m_serviceRoot->itemChanged(feeds()); } void FormStandardFeedDetails::loadFeedData() { FormFeedDetails::loadFeedData(); + if (m_isBatchEdit) { + // We hook batch selectors. + m_standardFeedDetails->m_ui.m_mcbDescription->addActionWidget(m_standardFeedDetails->m_ui.m_txtDescription); + m_standardFeedDetails->m_ui.m_mcbIcon->addActionWidget(m_standardFeedDetails->m_ui.m_btnIcon); + m_standardFeedDetails->m_ui.m_mcbParentCategory->addActionWidget(m_standardFeedDetails->m_ui.m_cmbParentCategory); + m_standardFeedDetails->m_ui.m_mcbPostProcessScript + ->addActionWidget(m_standardFeedDetails->m_ui.m_txtPostProcessScript); + m_standardFeedDetails->m_ui.m_mcbSourceType->addActionWidget(m_standardFeedDetails->m_ui.m_cmbSourceType); + m_standardFeedDetails->m_ui.m_mcbSource->addActionWidget(m_standardFeedDetails->m_ui.m_txtSource); + m_standardFeedDetails->m_ui.m_mcbTitle->addActionWidget(m_standardFeedDetails->m_ui.m_txtTitle); + m_standardFeedDetails->m_ui.m_mcbType->addActionWidget(m_standardFeedDetails->m_ui.m_cmbType); + m_standardFeedDetails->m_ui.m_mcbEncoding->addActionWidget(m_standardFeedDetails->m_ui.m_cmbEncoding); + + m_authDetails->m_mcbAuthType->addActionWidget(m_authDetails->m_cbAuthType); + m_authDetails->m_mcbAuthentication->addActionWidget(m_authDetails->m_gbAuthentication); + + m_standardFeedDetails->m_ui.m_btnFetchMetadata->setEnabled(false); + } + else { + // We hide batch selectors. + for (auto* cb : findChildren()) { + cb->hide(); + } + } + auto* std_feed = feed(); // Load categories. diff --git a/src/librssguard/services/standard/gui/standardfeeddetails.cpp b/src/librssguard/services/standard/gui/standardfeeddetails.cpp index 2c471e03d..2cdb9a9d1 100644 --- a/src/librssguard/services/standard/gui/standardfeeddetails.cpp +++ b/src/librssguard/services/standard/gui/standardfeeddetails.cpp @@ -348,6 +348,7 @@ void StandardFeedDetails::prepareForNewFeed(RootItem* parent_to_select, const QS // Make sure that "default" icon is used as the default option for new // feed. m_actionUseDefaultIcon->trigger(); + int default_encoding_index = m_ui.m_cmbEncoding->findText(QSL(DEFAULT_FEED_ENCODING)); if (default_encoding_index >= 0) { diff --git a/src/librssguard/services/standard/gui/standardfeeddetails.ui b/src/librssguard/services/standard/gui/standardfeeddetails.ui index bb815e0b6..d73a05ed8 100644 --- a/src/librssguard/services/standard/gui/standardfeeddetails.ui +++ b/src/librssguard/services/standard/gui/standardfeeddetails.ui @@ -6,7 +6,7 @@ 0 0 - 400 + 505 520
@@ -15,14 +15,21 @@ - - - Parent folder - - - m_cmbParentCategory - - + + + + + + + + Parent folder + + + m_cmbParentCategory + + + + @@ -41,14 +48,21 @@ - - - Type - - - m_cmbType - - + + + + + + + + Type + + + m_cmbType + + + + @@ -59,6 +73,9 @@ + + + @@ -69,45 +86,66 @@ - - - Title - - - m_txtTitle - - + + + + + + + + Title + + + m_txtTitle + + + + - - - Description - - - m_txtDescription - - + + + + + + + + Description + + + m_txtDescription + + + + - - - Source - - - m_txtSource - - + + + + + + + + Source + + + m_txtSource + + + + - + @@ -124,14 +162,21 @@ - - - Post-processing script - - - m_txtPostProcessScript - - + + + + + + + + Post-processing script + + + m_txtPostProcessScript + + + + @@ -187,14 +232,21 @@ - - - Icon - - - m_btnIcon - - + + + + + + + + Icon + + + m_btnIcon + + + + @@ -246,9 +298,17 @@ + + + + + MultiFeedEditCheckBox + QCheckBox +
multifeededitcheckbox.h
+
HelpSpoiler QWidget diff --git a/src/librssguard/services/standard/standardcategory.cpp b/src/librssguard/services/standard/standardcategory.cpp index 88326728f..7aff840f3 100644 --- a/src/librssguard/services/standard/standardcategory.cpp +++ b/src/librssguard/services/standard/standardcategory.cpp @@ -47,15 +47,6 @@ bool StandardCategory::canBeDeleted() const { return true; } -bool StandardCategory::editViaGui() { - QScopedPointer form_pointer(new FormCategoryDetails(serviceRoot(), - nullptr, - qApp->mainFormWidget())); - - form_pointer->addEditCategory(this); - return false; -} - bool StandardCategory::deleteViaGui() { if (removeItself()) { serviceRoot()->requestItemRemoval(this); diff --git a/src/librssguard/services/standard/standardcategory.h b/src/librssguard/services/standard/standardcategory.h index db952c09f..ab824b3b8 100644 --- a/src/librssguard/services/standard/standardcategory.h +++ b/src/librssguard/services/standard/standardcategory.h @@ -10,7 +10,7 @@ class StandardServiceRoot; class StandardCategory : public Category { - Q_OBJECT + Q_OBJECT public: explicit StandardCategory(RootItem* parent_item = nullptr); @@ -21,7 +21,6 @@ class StandardCategory : public Category { virtual Qt::ItemFlags additionalFlags() const; virtual bool performDragDropChange(RootItem* target_item); virtual bool canBeEdited() const; - virtual bool editViaGui(); virtual bool canBeDeleted() const; virtual bool deleteViaGui(); diff --git a/src/librssguard/services/standard/standardfeed.cpp b/src/librssguard/services/standard/standardfeed.cpp index 023ec1561..6046ea9ca 100644 --- a/src/librssguard/services/standard/standardfeed.cpp +++ b/src/librssguard/services/standard/standardfeed.cpp @@ -78,19 +78,6 @@ StandardServiceRoot* StandardFeed::serviceRoot() const { return qobject_cast(getParentServiceRoot()); } -bool StandardFeed::editViaGui() { - QScopedPointer form_pointer(new FormStandardFeedDetails(serviceRoot(), - nullptr, - {}, - qApp->mainFormWidget())); - - if (form_pointer->addEditFeed(this) != nullptr) { - setLastEtag({}); - } - - return false; -} - bool StandardFeed::deleteViaGui() { if (removeItself()) { serviceRoot()->requestItemRemoval(this); diff --git a/src/librssguard/services/standard/standardfeed.h b/src/librssguard/services/standard/standardfeed.h index 755cbdbc0..58548d7a8 100644 --- a/src/librssguard/services/standard/standardfeed.h +++ b/src/librssguard/services/standard/standardfeed.h @@ -46,7 +46,6 @@ class StandardFeed : public Feed { virtual QString additionalTooltip() const; virtual bool canBeDeleted() const; virtual bool deleteViaGui(); - virtual bool editViaGui(); virtual QVariantHash customDatabaseData() const; virtual void setCustomDatabaseData(const QVariantHash& data); virtual Qt::ItemFlags additionalFlags() const; diff --git a/src/librssguard/services/standard/standardserviceroot.cpp b/src/librssguard/services/standard/standardserviceroot.cpp index 3695de269..4ab10c9a6 100644 --- a/src/librssguard/services/standard/standardserviceroot.cpp +++ b/src/librssguard/services/standard/standardserviceroot.cpp @@ -113,11 +113,38 @@ bool StandardServiceRoot::canBeEdited() const { return true; } -bool StandardServiceRoot::editViaGui() { - FormEditStandardAccount form_pointer(qApp->mainFormWidget()); +FormAccountDetails* StandardServiceRoot::accountSetupDialog() const { + return new FormEditStandardAccount(qApp->mainFormWidget()); +} - form_pointer.addEditAccount(this); - return true; +void StandardServiceRoot::editItemsViaGui(const QList& items) { + auto std_feeds = boolinq::from(items) + .select([](RootItem* it) { + return qobject_cast(it); + }) + .where([](Feed* fd) { + return fd != nullptr; + }) + .toStdList(); + + if (!std_feeds.empty()) { + QScopedPointer form_pointer(new FormStandardFeedDetails(this, + nullptr, + {}, + qApp->mainFormWidget())); + + form_pointer->addEditFeed(FROM_STD_LIST(QList, std_feeds)); + return; + } + + if (items.first()->kind() == RootItem::Kind::ServiceRoot) { + QScopedPointer p(qobject_cast(accountSetupDialog())); + + p->addEditAccount(this); + return; + } + + ServiceRoot::editItemsViaGui(items); } bool StandardServiceRoot::supportsFeedAdding() const { diff --git a/src/librssguard/services/standard/standardserviceroot.h b/src/librssguard/services/standard/standardserviceroot.h index 95f2fc7bf..5430ada24 100644 --- a/src/librssguard/services/standard/standardserviceroot.h +++ b/src/librssguard/services/standard/standardserviceroot.h @@ -24,12 +24,13 @@ class StandardServiceRoot : public ServiceRoot { explicit StandardServiceRoot(RootItem* parent = nullptr); virtual ~StandardServiceRoot(); + virtual FormAccountDetails* accountSetupDialog() const; virtual void onDatabaseCleanup(); virtual void start(bool freshly_activated); virtual void stop(); virtual QString code() const; virtual bool canBeEdited() const; - virtual bool editViaGui(); + virtual void editItemsViaGui(const QList& items); virtual bool supportsFeedAdding() const; virtual bool supportsCategoryAdding() const; virtual Qt::ItemFlags additionalFlags() const; diff --git a/src/librssguard/services/tt-rss/ttrssserviceroot.cpp b/src/librssguard/services/tt-rss/ttrssserviceroot.cpp index b3f9571b1..c8f716181 100644 --- a/src/librssguard/services/tt-rss/ttrssserviceroot.cpp +++ b/src/librssguard/services/tt-rss/ttrssserviceroot.cpp @@ -75,11 +75,19 @@ bool TtRssServiceRoot::isSyncable() const { return true; } -bool TtRssServiceRoot::editViaGui() { - QScopedPointer form_pointer(new FormEditTtRssAccount(qApp->mainFormWidget())); +FormAccountDetails* TtRssServiceRoot::accountSetupDialog() const { + return new FormEditTtRssAccount(qApp->mainFormWidget()); +} - form_pointer->addEditAccount(this); - return true; +void TtRssServiceRoot::editItemsViaGui(const QList& items) { + if (items.first()->kind() == RootItem::Kind::ServiceRoot) { + QScopedPointer p(qobject_cast(accountSetupDialog())); + + p->addEditAccount(this); + return; + } + + ServiceRoot::editItemsViaGui(items); } bool TtRssServiceRoot::supportsFeedAdding() const { diff --git a/src/librssguard/services/tt-rss/ttrssserviceroot.h b/src/librssguard/services/tt-rss/ttrssserviceroot.h index bf8171370..c4ce702d4 100644 --- a/src/librssguard/services/tt-rss/ttrssserviceroot.h +++ b/src/librssguard/services/tt-rss/ttrssserviceroot.h @@ -26,7 +26,8 @@ class TtRssServiceRoot : public ServiceRoot, public CacheForServiceRoot { virtual QString code() const; virtual bool isSyncable() const; virtual bool canBeEdited() const; - virtual bool editViaGui(); + virtual void editItemsViaGui(const QList& items); + virtual FormAccountDetails* accountSetupDialog() const; virtual bool supportsFeedAdding() const; virtual bool supportsCategoryAdding() const; virtual void addNewFeed(RootItem* selected_item, const QString& url = QString());