diff --git a/src/actions/actionmanagerimpl.cpp b/src/actions/actionmanagerimpl.cpp index c88d12fc..e64844ea 100644 --- a/src/actions/actionmanagerimpl.cpp +++ b/src/actions/actionmanagerimpl.cpp @@ -1,700 +1,700 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "actionmanagerimpl.h" #include "akregatorconfig.h" #include "akregator_part.h" #include "articlelistview.h" #include "feed.h" #include "fetchqueue.h" #include "folder.h" #include "kernel.h" #include "mainwidget.h" #include "subscriptionlistview.h" #include "tabwidget.h" #include "trayicon.h" #include "treenode.h" #include "treenodevisitor.h" #include -#include -#include +#include +#include #include #include -#include +#include #include "akregator_debug.h" #include #include #include -#include -#include +#include +#include #include #include #include #include using namespace Akregator; class ActionManagerImpl::NodeSelectVisitor : public TreeNodeVisitor { public: NodeSelectVisitor(ActionManagerImpl *manager) : m_manager(manager) { } bool visitFeed(Feed *node) override { QAction *remove = m_manager->action(QStringLiteral("feed_remove")); if (remove) { remove->setEnabled(true); } QAction *hp = m_manager->action(QStringLiteral("feed_homepage")); if (hp) { hp->setEnabled(!node->htmlUrl().isEmpty()); } m_manager->action(QStringLiteral("feed_fetch"))->setText(i18n("&Fetch Feed")); m_manager->action(QStringLiteral("feed_remove"))->setText(i18n("&Delete Feed")); m_manager->action(QStringLiteral("feed_modify"))->setText(i18n("&Edit Feed...")); m_manager->action(QStringLiteral("feed_mark_all_as_read"))->setText(i18n("&Mark Feed as Read")); return true; } bool visitFolder(Folder *node) override { QAction *remove = m_manager->action(QStringLiteral("feed_remove")); if (remove) { remove->setEnabled(node->parent()); // root nodes must not be deleted } QAction *hp = m_manager->action(QStringLiteral("feed_homepage")); if (hp) { hp->setEnabled(false); } m_manager->action(QStringLiteral("feed_fetch"))->setText(i18n("&Fetch Feeds")); m_manager->action(QStringLiteral("feed_remove"))->setText(i18n("&Delete Folder")); m_manager->action(QStringLiteral("feed_modify"))->setText(i18n("&Rename Folder")); m_manager->action(QStringLiteral("feed_mark_all_as_read"))->setText(i18n("&Mark Feeds as Read")); return true; } private: ActionManagerImpl *m_manager = nullptr; }; class ActionManagerImpl::ActionManagerImplPrivate { public: QString quickSearchLineText() const; NodeSelectVisitor *nodeSelectVisitor = nullptr; ArticleListView *articleList = nullptr; SubscriptionListView *subscriptionListView = nullptr; MainWidget *mainWidget = nullptr; Part *part = nullptr; TrayIcon *trayIcon = nullptr; KActionMenu *tagMenu = nullptr; KActionCollection *actionCollection = nullptr; TabWidget *tabWidget = nullptr; PimCommon::ShareServiceUrlManager *shareServiceManager = nullptr; WebEngineViewer::ZoomActionMenu *zoomActionMenu = nullptr; QAction *mQuickSearchAction = nullptr; }; void ActionManagerImpl::slotSettingsChanged() { QAction *a = action(QStringLiteral("feed_hide_read")); if (!a) { qCCritical(AKREGATOR_LOG) << "Action not found"; return; } a->setChecked(Settings::hideReadFeeds()); a = action(QStringLiteral("auto_expand_folders")); if (!a) { qCCritical(AKREGATOR_LOG) << "Action not found"; return; } a->setChecked(Settings::autoExpandFolders()); } void ActionManagerImpl::slotNodeSelected(TreeNode *node) { if (node) { d->nodeSelectVisitor->visit(node); } } ActionManagerImpl::ActionManagerImpl(Part *part, QObject *parent) : ActionManager(parent) , d(new ActionManagerImplPrivate) { d->nodeSelectVisitor = new NodeSelectVisitor(this); d->part = part; d->subscriptionListView = nullptr; d->articleList = nullptr; d->trayIcon = nullptr; d->mainWidget = nullptr; d->tabWidget = nullptr; d->tagMenu = nullptr; d->actionCollection = part->actionCollection(); d->shareServiceManager = new PimCommon::ShareServiceUrlManager(this); initPart(); } ActionManagerImpl::~ActionManagerImpl() { delete d->nodeSelectVisitor; delete d; d = nullptr; } void ActionManagerImpl::setTrayIcon(TrayIcon *trayIcon) { if (trayIcon == nullptr) { d->trayIcon = nullptr; return; } if (d->trayIcon) { return; } else { d->trayIcon = trayIcon; } QMenu *traypop = trayIcon->contextMenu(); if (QAction *act = actionCollection()->action(QStringLiteral("feed_fetch_all"))) { traypop->addAction(act); } if (QAction *act = actionCollection()->action(QStringLiteral("options_configure"))) { traypop->addAction(act); } } void ActionManagerImpl::initPart() { QAction *action = d->actionCollection->addAction(QStringLiteral("file_import")); action->setText(i18n("&Import Feeds...")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); connect(action, &QAction::triggered, d->part, &Part::fileImport); action = d->actionCollection->addAction(QStringLiteral("file_export")); action->setText(i18n("&Export Feeds...")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); connect(action, &QAction::triggered, d->part, &Part::fileExport); QAction *configure = d->actionCollection->addAction(QStringLiteral("options_configure")); configure->setText(i18n("&Configure Akregator...")); configure->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(configure, &QAction::triggered, d->part, &Part::showOptions); KStandardAction::configureNotifications(d->part, &Part::showNotificationOptions, d->actionCollection); // options_configure_notifications } void ActionManagerImpl::initMainWidget(MainWidget *mainWidget) { if (d->mainWidget) { return; } d->mainWidget = mainWidget; KActionCollection *coll = actionCollection(); // Feed/Feed Group popup menu QAction *action = coll->addAction(QStringLiteral("feed_homepage")); action->setText(i18n("&Open Homepage")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotOpenHomepage); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_H)); action = coll->addAction(QStringLiteral("reload_all_tabs")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); action->setText(i18n("Reload All Tabs")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotReloadAllTabs); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::Key_F5)); action = coll->addAction(QStringLiteral("feed_add")); action->setIcon(QIcon::fromTheme(QStringLiteral("feed-subscribe"))); action->setText(i18n("&Add Feed...")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFeedAdd); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_Insert)); action = coll->addAction(QStringLiteral("feed_add_group")); action->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); action->setText(i18n("Ne&w Folder...")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFeedAddGroup); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::Key_Insert)); action = coll->addAction(QStringLiteral("feed_remove")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action->setText(i18n("&Delete Feed")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFeedRemove); coll->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Delete)); action = coll->addAction(QStringLiteral("feed_modify")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); action->setText(i18n("&Edit Feed...")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFeedModify); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_F2)); // toolbar / View const MainWidget::ViewMode viewMode = static_cast(Settings::viewMode()); QActionGroup *group = new QActionGroup(this); action = coll->addAction(QStringLiteral("normal_view")); action->setCheckable(true); action->setChecked(viewMode == MainWidget::NormalView); group->addAction(action); action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom"))); action->setText(i18n("&Normal View")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotNormalView); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_1)); action = coll->addAction(QStringLiteral("widescreen_view")); action->setCheckable(true); action->setChecked(viewMode == MainWidget::WidescreenView); group->addAction(action); action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); action->setText(i18n("&Widescreen View")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotWidescreenView); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_2)); action = coll->addAction(QStringLiteral("combined_view")); action->setCheckable(true); action->setChecked(viewMode == MainWidget::CombinedView); group->addAction(action); action->setIcon(QIcon::fromTheme(QStringLiteral("view-list-text"))); action->setText(i18n("C&ombined View")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotCombinedView); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3)); // toolbar / feed menu action = coll->addAction(QStringLiteral("feed_fetch")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); action->setText(i18n("&Fetch Feed")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFetchCurrentFeed); coll->setDefaultShortcuts(action, KStandardShortcut::shortcut(KStandardShortcut::Reload)); action = coll->addAction(QStringLiteral("feed_fetch_all")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-bottom"))); action->setText(i18n("Fe&tch All Feeds")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotFetchAllFeeds); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L)); QAction *stopAction = coll->addAction(QStringLiteral("feed_stop")); stopAction->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); stopAction->setText(i18n("C&ancel Feed Fetches")); connect(stopAction, &QAction::triggered, Kernel::self()->fetchQueue(), &FetchQueue::slotAbort); coll->setDefaultShortcut(stopAction, QKeySequence(Qt::Key_Escape)); stopAction->setEnabled(false); action = coll->addAction(QStringLiteral("feed_mark_all_as_read")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-read"))); action->setText(i18n("&Mark Feed as Read")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotMarkAllRead); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_R)); action = coll->addAction(QStringLiteral("feed_mark_all_feeds_as_read")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-read"))); action->setText(i18n("Ma&rk All Feeds as Read")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotMarkAllFeedsRead); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_R)); // Settings menu KToggleAction *sqf = coll->add(QStringLiteral("show_quick_filter")); sqf->setText(i18n("Show Quick Filter")); connect(sqf, &QAction::triggered, d->mainWidget, &MainWidget::slotToggleShowQuickFilter); sqf->setChecked(Settings::showQuickFilter()); action = coll->addAction(QStringLiteral("article_open")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); action->setText(i18n("Open in Tab")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotOpenSelectedArticles); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::Key_Return)); action = coll->addAction(QStringLiteral("article_open_in_background")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); action->setText(i18n("Open in Background Tab")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotOpenSelectedArticlesInBackground); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_Return)); action = coll->addAction(QStringLiteral("article_open_external")); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); action->setText(i18n("Open in External Browser")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotOpenSelectedArticlesInBrowser); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Return)); action = coll->addAction(QStringLiteral("article_copy_link_address")); action->setText(i18n("Copy Link Address")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotCopyLinkAddress); action = coll->addAction(QStringLiteral("go_prev_unread_article")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); action->setText(i18n("Pre&vious Unread Article")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotPrevUnreadArticle); coll->setDefaultShortcuts(action, QList() << QKeySequence(Qt::Key_Minus) << QKeySequence(Qt::Key_Minus + Qt::KeypadModifier)); action = coll->addAction(QStringLiteral("go_next_unread_article")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); action->setText(i18n("Ne&xt Unread Article")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotNextUnreadArticle); coll->setDefaultShortcuts(action, QList() << QKeySequence(Qt::Key_Plus) << QKeySequence(Qt::Key_Plus + Qt::KeypadModifier) << QKeySequence(Qt::Key_Equal) << QKeySequence(Qt::Key_Equal + Qt::KeypadModifier)); action = coll->addAction(QStringLiteral("article_delete")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); action->setText(i18n("&Delete")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotArticleDelete); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_Delete)); KActionMenu *statusMenu = coll->add(QStringLiteral("article_set_status")); statusMenu->setText(i18n("&Mark As")); statusMenu->setEnabled(false); action = coll->addAction(QStringLiteral("article_set_status_read")); action->setText(i18nc("as in: mark as read", "&Read")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-read"))); action->setToolTip(i18n("Mark selected article as read")); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_E)); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotSetSelectedArticleRead); statusMenu->addAction(action); action = coll->addAction(QStringLiteral("article_set_status_new")); action->setText(i18nc("as in: mark as new", "&New")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-unread-new"))); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_N)); action->setToolTip(i18n("Mark selected article as new")); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotSetSelectedArticleNew); statusMenu->addAction(action); action = coll->addAction(QStringLiteral("article_set_status_unread")); action->setText(i18nc("as in: mark as unread", "&Unread")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-unread"))); action->setToolTip(i18n("Mark selected article as unread")); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_U)); connect(action, &QAction::triggered, d->mainWidget, &MainWidget::slotSetSelectedArticleUnread); statusMenu->addAction(action); KToggleAction *importantAction = coll->add(QStringLiteral("article_set_status_important")); importantAction->setText(i18n("&Mark as Important")); importantAction->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-important"))); const QList importantSC = {QKeySequence(Qt::CTRL + Qt::Key_I), QKeySequence(Qt::Key_I)}; coll->setDefaultShortcuts(importantAction, importantSC); importantAction->setCheckedState(KGuiItem(i18n("Remove &Important Mark"))); connect(importantAction, &QAction::triggered, d->mainWidget, &MainWidget::slotArticleToggleKeepFlag); action = coll->addAction(QStringLiteral("feedstree_move_up")); action->setText(i18n("Move Node Up")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotMoveCurrentNodeUp); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Up)); action = coll->addAction(QStringLiteral("feedstree_move_down")); action->setText(i18n("Move Node Down")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotMoveCurrentNodeDown); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Down)); action = coll->addAction(QStringLiteral("move_node_left")); action->setText(i18n("Move Node Left")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotMoveCurrentNodeLeft); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Left)); action = coll->addAction(QStringLiteral("feedstree_move_right")); action->setText(i18n("Move Node Right")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotMoveCurrentNodeRight); coll->setDefaultShortcut(action, QKeySequence(Qt::SHIFT + Qt::ALT + Qt::Key_Right)); action = coll->addAction(QStringLiteral("file_sendlink")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-message-new"))); action->setText(i18n("Send &Link Address...")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotSendLink); action = coll->addAction(QStringLiteral("file_sendfile")); action->setIcon(QIcon::fromTheme(QStringLiteral("mail-message-new"))); action->setText(i18n("Send &File...")); connect(action, &QAction::triggered, mainWidget, &MainWidget::slotSendFile); coll->addAction(QStringLiteral("share_serviceurl"), d->shareServiceManager->menu()); connect(d->shareServiceManager, &PimCommon::ShareServiceUrlManager::serviceUrlSelected, this, &ActionManagerImpl::slotServiceUrlSelected); d->mQuickSearchAction = new QAction(i18n("Set Focus to Quick Search"), this); //If change shortcut change Panel::setQuickSearchClickMessage(...) message actionCollection()->setDefaultShortcut(d->mQuickSearchAction, QKeySequence(Qt::ALT + Qt::Key_Q)); actionCollection()->addAction(QStringLiteral("focus_to_quickseach"), d->mQuickSearchAction); connect(d->mQuickSearchAction, &QAction::triggered, mainWidget, &MainWidget::slotFocusQuickSearch); setArticleActionsEnabled(false); } void ActionManagerImpl::slotServiceUrlSelected(PimCommon::ShareServiceUrlManager::ServiceType type) { if (d->mainWidget) { QString title; QString link; d->mainWidget->currentArticleInfo(link, title); const QUrl url = d->shareServiceManager->generateServiceUrl(link, title, type); d->shareServiceManager->openUrl(url); } } void ActionManagerImpl::initArticleListView(ArticleListView *articleList) { if (d->articleList) { return; } else { d->articleList = articleList; } QAction *action = actionCollection()->addAction(QStringLiteral("go_previous_article")); action->setText(i18n("&Previous Article")); connect(action, &QAction::triggered, articleList, &ArticleListView::slotPreviousArticle); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Left)); action = actionCollection()->addAction(QStringLiteral("go_next_article")); action->setText(i18n("&Next Article")); connect(action, &QAction::triggered, articleList, &ArticleListView::slotNextArticle); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Right)); } void ActionManagerImpl::initSubscriptionListView(SubscriptionListView *subscriptionListView) { if (d->subscriptionListView) { return; } else { d->subscriptionListView = subscriptionListView; } KActionCollection *coll = actionCollection(); QAction *action = coll->addAction(QStringLiteral("go_prev_feed")); action->setText(i18n("&Previous Feed")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotPrevFeed); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_P)); action = coll->addAction(QStringLiteral("go_next_feed")); action->setText(i18n("&Next Feed")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotNextFeed); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_N)); action = coll->addAction(QStringLiteral("go_next_unread_feed")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); action->setText(i18n("N&ext Unread Feed")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotNextUnreadFeed); coll->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Plus)); action = coll->addAction(QStringLiteral("go_prev_unread_feed")); action->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); action->setText(i18n("Prev&ious Unread Feed")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotPrevUnreadFeed); coll->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Minus)); action = coll->addAction(QStringLiteral("feedstree_home")); action->setText(i18n("Go to Top of Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemBegin); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Home)); action = coll->addAction(QStringLiteral("feedstree_end")); action->setText(i18n("Go to Bottom of Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemEnd); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_End)); action = coll->addAction(QStringLiteral("feedstree_left")); action->setText(i18n("Go Left in Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemLeft); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Left)); action = coll->addAction(QStringLiteral("feedstree_right")); action->setText(i18n("Go Right in Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemRight); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Right)); action = coll->addAction(QStringLiteral("feedstree_up")); action->setText(i18n("Go Up in Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemUp); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Up)); action = coll->addAction(QStringLiteral("feedstree_down")); action->setText(i18n("Go Down in Tree")); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotItemDown); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Down)); action = coll->addAction(QStringLiteral("feed_hide_read")); action->setCheckable(true); action->setText(i18n("Hide Read Feeds")); action->setChecked(Settings::hideReadFeeds()); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotSetHideReadFeeds); action = coll->addAction(QStringLiteral("auto_expand_folders")); action->setCheckable(true); action->setText(i18n("Auto-expand folders with unread articles")); action->setChecked(Settings::autoExpandFolders()); connect(action, &QAction::triggered, subscriptionListView, &SubscriptionListView::slotSetAutoExpandFolders); } void ActionManagerImpl::initTabWidget(TabWidget *tabWidget) { if (d->tabWidget) { return; } else { d->tabWidget = tabWidget; } KActionCollection *coll = actionCollection(); QAction *action = coll->addAction(QStringLiteral("select_next_tab")); action->setText(i18n("Select Next Tab")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotNextTab); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Period)); action = coll->addAction(QStringLiteral("select_previous_tab")); action->setText(i18n("Select Previous Tab")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotPreviousTab); coll->setDefaultShortcut(action, QKeySequence(Qt::Key_Comma + Qt::CTRL)); action = coll->addAction(QStringLiteral("tab_detach")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); action->setText(i18n("Detach Tab")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotDetachTab); coll->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B)); action = KStandardAction::copy(d->tabWidget, &TabWidget::slotCopy, coll); coll->addAction(QStringLiteral("viewer_copy"), action); action = KStandardAction::print(d->tabWidget, &TabWidget::slotPrint, coll); coll->addAction(QStringLiteral("viewer_print"), action); action = KStandardAction::printPreview(d->tabWidget, &TabWidget::slotPrintPreview, coll); coll->addAction(QStringLiteral("viewer_printpreview"), action); action = coll->addAction(QStringLiteral("tab_mute")); action->setText(i18n("Mute")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotMute); action = coll->addAction(QStringLiteral("tab_unmute")); action->setText(i18n("Unmute")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotUnMute); action = new QAction(i18n("Speak Text"), this); action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech"))); coll->addAction(QStringLiteral("speak_text"), action); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotTextToSpeech); action = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("&Find in Message..."), this); coll->addAction(QStringLiteral("find_in_messages"), action); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotFindTextInHtml); coll->setDefaultShortcut(action, KStandardShortcut::find().first()); action = coll->addAction(QStringLiteral("tab_copylinkaddress")); action->setText(i18n("Copy Link Address")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotCopyLinkAddress); action = coll->addAction(QStringLiteral("tab_remove")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); action->setText(i18n("Close Tab")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotCloseTab); coll->setDefaultShortcuts(action, KStandardShortcut::close()); d->zoomActionMenu = new WebEngineViewer::ZoomActionMenu(this); connect(d->zoomActionMenu, &WebEngineViewer::ZoomActionMenu::zoomChanged, d->tabWidget, &TabWidget::slotZoomChanged); d->zoomActionMenu->setActionCollection(coll); d->zoomActionMenu->createZoomActions(); QString actionname; for (int i = 1; i < 10; ++i) { actionname = QString::asprintf("activate_tab_%02d", i); action = new QAction(i18n("Activate Tab %1", i), this); coll->addAction(actionname, action); coll->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+%1").arg(i))); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotActivateTab); } action = coll->addAction(QStringLiteral("savelinkas")); action->setText(i18n("&Save Link As...")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotSaveLinkAs); action = coll->addAction(QStringLiteral("copylinkaddress")); action->setText(i18n("Copy &Link Address")); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotCopyLinkAddress); action = new QAction(i18n("Copy Image Location"), this); action->setIcon(QIcon::fromTheme(QStringLiteral("view-media-visualization"))); coll->addAction(QStringLiteral("copy_image_location"), action); coll->setShortcutsConfigurable(action, false); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotCopyImageLocation); // save Image On Disk action = new QAction(i18n("Save Image On Disk..."), this); coll->addAction(QStringLiteral("saveas_imageurl"), action); coll->setShortcutsConfigurable(action, false); connect(action, &QAction::triggered, d->tabWidget, &TabWidget::slotSaveImageOnDisk); } QWidget *ActionManagerImpl::container(const QString &name) { if (d->part->factory()) { return d->part->factory()->container(name, d->part); } else { return nullptr; } } KActionCollection *ActionManagerImpl::actionCollection() const { return d->actionCollection; } QAction *ActionManagerImpl::action(const QString &name) { return d->actionCollection != nullptr ? d->actionCollection->action(name) : nullptr; } void ActionManagerImpl::setArticleActionsEnabled(bool enabled) { #undef setActionEnabled #define setActionEnabled(name) { QAction *const a = action(name); if (a) {a->setEnabled(enabled);}} setActionEnabled(QStringLiteral("article_open")) setActionEnabled(QStringLiteral("article_open_external")) setActionEnabled(QStringLiteral("article_set_status_important")) setActionEnabled(QStringLiteral("article_set_status")) setActionEnabled(QStringLiteral("article_delete")) setActionEnabled(QStringLiteral("file_sendlink")) setActionEnabled(QStringLiteral("file_sendfile")) setActionEnabled(QStringLiteral("article_open_in_background")) setActionEnabled(QStringLiteral("share_serviceurl")) #undef setActionEnabled } WebEngineViewer::ZoomActionMenu *ActionManagerImpl::zoomActionMenu() const { return d->zoomActionMenu; } QString ActionManagerImpl::quickSearchLineText() const { return d->quickSearchLineText(); } QString ActionManagerImpl::ActionManagerImplPrivate::quickSearchLineText() const { return mQuickSearchAction->shortcut().toString(); } diff --git a/src/addfeeddialog.cpp b/src/addfeeddialog.cpp index 3954f018..18920894 100644 --- a/src/addfeeddialog.cpp +++ b/src/addfeeddialog.cpp @@ -1,150 +1,150 @@ /* This file is part of Akregator. Copyright (C) 2004 Stanislav Karchebny This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "addfeeddialog.h" #include "feed.h" #include "kernel.h" #include "akregator_debug.h" -#include -#include +#include +#include #include -#include +#include #include #include #include #include using namespace Akregator; AddFeedWidget::AddFeedWidget(QWidget *parent) : QWidget(parent) { setupUi(this); pixmapLabel1->setPixmap(QIcon::fromTheme(QStringLiteral("applications-internet")).pixmap(IconSize(KIconLoader::Desktop), IconSize(KIconLoader::Desktop))); statusLabel->setText(QString()); } AddFeedWidget::~AddFeedWidget() { } QSize AddFeedDialog::sizeHint() const { QSize sh = QDialog::sizeHint(); sh.setHeight(minimumSize().height()); sh.setWidth(sh.width() * 1.2); return sh; } Feed *AddFeedDialog::feed() const { return m_feed; } AddFeedDialog::AddFeedDialog(QWidget *parent, const QString &name) : QDialog(parent) { setObjectName(name); setWindowTitle(i18n("Add Feed")); QVBoxLayout *mainLayout = new QVBoxLayout(this); widget = new AddFeedWidget(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); mOkButton = buttonBox->button(QDialogButtonBox::Ok); mOkButton->setDefault(true); mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &AddFeedDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &AddFeedDialog::reject); mainLayout->addWidget(widget); mainLayout->addWidget(buttonBox); widget->urlEdit->setFocus(); connect(widget->urlEdit, &QLineEdit::textChanged, this, &AddFeedDialog::textChanged); mOkButton->setEnabled(false); } AddFeedDialog::~AddFeedDialog() { } void AddFeedDialog::setUrl(const QString &t) { widget->urlEdit->setText(t); } void AddFeedDialog::accept() { mOkButton->setEnabled(false); mFeedUrl = widget->urlEdit->text().trimmed(); delete m_feed; m_feed = new Feed(Kernel::self()->storage()); // HACK: make weird wordpress links ("feed:http://foobar/rss") work if (mFeedUrl.startsWith(QLatin1String("feed:http"))) { mFeedUrl = mFeedUrl.right(mFeedUrl.length() - 5); } if (!mFeedUrl.contains(QLatin1String(":/"))) { mFeedUrl.prepend(QLatin1String("https://")); } QUrl asUrl(mFeedUrl); if (asUrl.scheme() == QLatin1String("feed")) { asUrl.setScheme(QStringLiteral("https")); mFeedUrl = asUrl.url(); } m_feed->setXmlUrl(mFeedUrl); widget->statusLabel->setText(i18n("Downloading %1", mFeedUrl)); connect(m_feed, &Feed::fetched, this, &AddFeedDialog::fetchCompleted); connect(m_feed, &Feed::fetchError, this, &AddFeedDialog::fetchError); connect(m_feed, &Feed::fetchDiscovery, this, &AddFeedDialog::fetchDiscovery); m_feed->fetch(true); } void AddFeedDialog::fetchCompleted(Feed * /*f*/) { QDialog::accept(); } void AddFeedDialog::fetchError(Feed *) { KMessageBox::error(this, i18n("Feed not found from %1.", mFeedUrl)); QDialog::reject(); } void AddFeedDialog::fetchDiscovery(Feed *f) { widget->statusLabel->setText(i18n("Feed found, downloading...")); mFeedUrl = f->xmlUrl(); } void AddFeedDialog::textChanged(const QString &text) { mOkButton->setEnabled(!text.trimmed().isEmpty()); } diff --git a/src/akregator_part.cpp b/src/akregator_part.cpp index 39d565c7..681764ee 100644 --- a/src/akregator_part.cpp +++ b/src/akregator_part.cpp @@ -1,845 +1,845 @@ /* This file is part of Akregator. Copyright (C) 2004 Stanislav Karchebny 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "akregator_part.h" #include "akregator_debug.h" #include "messageviewer/messageviewersettings.h" #include "akregatorconfig.h" #include "aboutdata.h" #include "actionmanagerimpl.h" #include "article.h" #include "fetchqueue.h" #include "feedlist.h" #include "framemanager.h" #include "kernel.h" #include "loadfeedlistcommand.h" #include "mainwidget.h" #include "notificationmanager.h" #include "plugin.h" #include "pluginmanager.h" #include "storage.h" #include "storagefactory.h" #include "storagefactoryregistry.h" #include "trayicon.h" #include "widgets/akregatorcentralwidget.h" #include "dummystorage/storagefactorydummyimpl.h" #include "utils.h" #include "akregator_options.h" #include #include "akregator-version.h" #include "unityservicemanager.h" #include -#include -#include -#include -#include +#include +#include +#include +#include #include -#include +#include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include #include #include "akregratormigrateapplication.h" #include "partadaptor.h" #include #include #include namespace { static QDomDocument createDefaultFeedList() { QDomDocument doc; QDomProcessingInstruction z = doc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"UTF-8\"")); doc.appendChild(z); QDomElement root = doc.createElement(QStringLiteral("opml")); root.setAttribute(QStringLiteral("version"), QStringLiteral("1.0")); doc.appendChild(root); QDomElement head = doc.createElement(QStringLiteral("head")); root.appendChild(head); QDomElement text = doc.createElement(QStringLiteral("text")); text.appendChild(doc.createTextNode(i18n("Feeds"))); head.appendChild(text); QDomElement body = doc.createElement(QStringLiteral("body")); root.appendChild(body); QDomElement mainFolder = doc.createElement(QStringLiteral("outline")); mainFolder.setAttribute(QStringLiteral("text"), QStringLiteral("KDE")); body.appendChild(mainFolder); QDomElement dot = doc.createElement(QStringLiteral("outline")); dot.setAttribute(QStringLiteral("text"), i18n("KDE Dot News")); dot.setAttribute(QStringLiteral("xmlUrl"), QStringLiteral("https://dot.kde.org/rss.xml")); mainFolder.appendChild(dot); QDomElement linuxFeeds = doc.createElement(QStringLiteral("outline")); linuxFeeds.setAttribute(QStringLiteral("text"), i18n("Linux.com")); linuxFeeds.setAttribute(QStringLiteral("xmlUrl"), QStringLiteral("https://www.linux.com/feed/")); mainFolder.appendChild(linuxFeeds); QDomElement planetkde = doc.createElement(QStringLiteral("outline")); planetkde.setAttribute(QStringLiteral("text"), i18n("Planet KDE")); planetkde.setAttribute(QStringLiteral("xmlUrl"), QStringLiteral("https://planetkde.org/rss20.xml")); mainFolder.appendChild(planetkde); QDomElement apps = doc.createElement(QStringLiteral("outline")); apps.setAttribute(QStringLiteral("text"), i18n("KDE Apps")); apps.setAttribute(QStringLiteral("xmlUrl"), QStringLiteral("https://store.kde.org/content.rdf")); mainFolder.appendChild(apps); // spanish feed(s) QDomElement spanishFolder = doc.createElement(QStringLiteral("outline")); spanishFolder.setAttribute(QStringLiteral("text"), i18n("Spanish feeds")); mainFolder.appendChild(spanishFolder); QDomElement spanishKde = doc.createElement(QStringLiteral("outline")); spanishKde.setAttribute(QStringLiteral("text"), i18n("Planet KDE EspaƱa")); spanishKde.setAttribute(QStringLiteral("xmlUrl"), QStringLiteral("https://planet.kde-espana.org/atom.xml")); spanishFolder.appendChild(spanishKde); return doc; } } namespace Akregator { K_PLUGIN_FACTORY(AkregatorFactory, registerPlugin(); ) static Part *mySelf = nullptr; BrowserExtension::BrowserExtension(Part *p, const char *name) : KParts::BrowserExtension(p) { AkregratorMigrateApplication migrate; migrate.migrate(); setObjectName(QLatin1String(name)); m_part = p; } void BrowserExtension::saveSettings() { m_part->saveSettings(); } Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList &) : KParts::ReadOnlyPart(parent) , m_standardListLoaded(false) , m_shuttingDown(false) , m_doCrashSave(false) , m_backedUpList(false) , m_mainWidget(nullptr) , m_storage(nullptr) , m_dialog(nullptr) { mySelf = this; //Make sure to initialize settings Part::config(); initFonts(); setPluginLoadingMode(LoadPluginsIfEnabled); setPluginInterfaceVersion(AKREGATOR_PLUGIN_INTERFACE_VERSION); setComponentName(QStringLiteral("akregator"), i18n("Akregator")); setXMLFile(QStringLiteral("akregator_part.rc"), true); new PartAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Akregator"), this); const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/akregator/data/"); QDir().mkpath(path); m_standardFeedList = path + QStringLiteral("/feeds.opml"); Backend::StorageFactoryDummyImpl *dummyFactory = new Backend::StorageFactoryDummyImpl(); if (!Backend::StorageFactoryRegistry::self()->registerFactory(dummyFactory, dummyFactory->key())) { // There was already a dummy factory registered. delete dummyFactory; } loadPlugins(QStringLiteral("storage")); // FIXME: also unload them! m_storage = nullptr; Backend::StorageFactory *storageFactory = Backend::StorageFactoryRegistry::self()->getFactory(Settings::archiveBackend()); if (storageFactory != nullptr) { m_storage = storageFactory->createStorage(QStringList()); } if (!m_storage) { // Houston, we have a problem m_storage = Backend::StorageFactoryRegistry::self()->getFactory(QStringLiteral("dummy"))->createStorage(QStringList()); KMessageBox::error(parentWidget, i18n("Unable to load storage backend plugin \"%1\". No feeds are archived.", Settings::archiveBackend()), i18n("Plugin error")); } m_storage->open(true); Kernel::self()->setStorage(m_storage); m_actionManager = new ActionManagerImpl(this); ActionManager::setInstance(m_actionManager); mCentralWidget = new Akregator::AkregatorCentralWidget(parentWidget); connect(mCentralWidget, &AkregatorCentralWidget::restoreSession, this, &Part::slotRestoreSession); m_mainWidget = new Akregator::MainWidget(this, parentWidget, m_actionManager, QStringLiteral("akregator_view")); mCentralWidget->setMainWidget(m_mainWidget); m_extension = new BrowserExtension(this, "ak_extension"); connect(Kernel::self()->frameManager(), &FrameManager::signalCaptionChanged, this, &Part::setWindowCaption); connect(Kernel::self()->frameManager(), &FrameManager::signalStatusText, this, &Part::slotSetStatusText); connect(Kernel::self()->frameManager(), &FrameManager::signalLoadingProgress, m_extension, &BrowserExtension::loadingProgress); connect(Kernel::self()->frameManager(), &FrameManager::signalCanceled, this, &ReadOnlyPart::canceled); connect(Kernel::self()->frameManager(), &FrameManager::signalStarted, this, &Part::slotStarted); connect(Kernel::self()->frameManager(), SIGNAL(signalCompleted()), this, SIGNAL(completed())); // notify the part that this is our internal widget setWidget(mCentralWidget); //Initialize instance. (void)UnityServiceManager::instance(); if (Settings::showUnreadInTaskbar()) { connect(m_mainWidget.data(), &MainWidget::signalUnreadCountChanged, UnityServiceManager::instance(), &UnityServiceManager::slotSetUnread); } if (Settings::showTrayIcon() && !TrayIcon::getInstance()) { initializeTrayIcon(); QWidget *const notificationParent = isTrayIconEnabled() ? m_mainWidget->window() : nullptr; NotificationManager::self()->setWidget(notificationParent, componentData().componentName()); } connect(qApp, &QCoreApplication::aboutToQuit, this, &Part::slotOnShutdown); m_autosaveTimer = new QTimer(this); connect(m_autosaveTimer, &QTimer::timeout, this, &Part::slotSaveFeedList); m_autosaveTimer->start(5 * 60 * 1000); // 5 minutes loadPlugins(QStringLiteral("extension")); // FIXME: also unload them! if (mCentralWidget->previousSessionCrashed()) { mCentralWidget->needToRestoreCrashedSession(); } else { m_doCrashSave = true; autoReadProperties(); } } KSharedConfig::Ptr Part::config() { assert(mySelf); if (!mySelf->mConfig) { mySelf->mConfig = KSharedConfig::openConfig(QStringLiteral("akregatorrc")); } return mySelf->mConfig; } void Part::updateQuickSearchLineText() { if (m_mainWidget) { m_mainWidget->updateQuickSearchLineText(); } } void Part::loadPlugins(const QString &type) { const KService::List offers = PluginManager::query(QStringLiteral("[X-KDE-akregator-plugintype] == '%1'").arg(type)); for (const KService::Ptr &i : offers) { Akregator::Plugin *plugin = PluginManager::createFromService(i, this); if (!plugin) { continue; } plugin->initialize(); plugin->insertGuiClients(this); } } void Part::slotStarted() { Q_EMIT started(nullptr); } void Part::slotOnShutdown() { autoSaveProperties(); m_shuttingDown = true; m_autosaveTimer->stop(); if (m_mainWidget) { saveSettings(); m_mainWidget->slotOnShutdown(); } //delete m_mainWidget; delete TrayIcon::getInstance(); TrayIcon::setInstance(nullptr); delete m_storage; m_storage = nullptr; //delete m_actionManager; } void Part::initializeTrayIcon() { TrayIcon *trayIcon = new TrayIcon(m_mainWidget->window()); TrayIcon::setInstance(trayIcon); m_actionManager->setTrayIcon(trayIcon); if (isTrayIconEnabled()) { trayIcon->setStatus(KStatusNotifierItem::Active); } connect(m_mainWidget.data(), &MainWidget::signalUnreadCountChanged, trayIcon, &TrayIcon::slotSetUnread); connect(m_mainWidget.data(), &MainWidget::signalArticlesSelected, this, &Part::signalArticlesSelected); m_mainWidget->slotSetTotalUnread(); } void Part::slotSettingsChanged() { if (Settings::showUnreadInTaskbar()) { connect(m_mainWidget.data(), &MainWidget::signalUnreadCountChanged, UnityServiceManager::instance(), &UnityServiceManager::slotSetUnread); m_mainWidget->slotSetTotalUnread(); } else { disconnect(m_mainWidget.data(), &MainWidget::signalUnreadCountChanged, UnityServiceManager::instance(), &UnityServiceManager::slotSetUnread); UnityServiceManager::instance()->slotSetUnread(0); } NotificationManager::self()->setWidget(isTrayIconEnabled() ? m_mainWidget->window() : nullptr, componentData().componentName()); if (Settings::showTrayIcon()) { if (!TrayIcon::getInstance()) { initializeTrayIcon(); m_mainWidget->slotSetTotalUnread(); } } else { TrayIcon::getInstance()->disconnect(); delete TrayIcon::getInstance(); TrayIcon::setInstance(nullptr); m_actionManager->setTrayIcon(nullptr); } const QStringList fonts { Settings::standardFont(), Settings::fixedFont(), Settings::sansSerifFont(), Settings::serifFont(), Settings::standardFont(), Settings::standardFont(), QStringLiteral("0") }; Settings::setFonts(fonts); if (Settings::minimumFontSize() > Settings::mediumFontSize()) { Settings::setMediumFontSize(Settings::minimumFontSize()); } saveSettings(); Q_EMIT signalSettingsChanged(); initFonts(); } void Part::slotSetStatusText(const QString &statusText) { KPIM::BroadcastStatus::instance()->setStatusMsg(statusText); } void Part::saveSettings() { if (m_mainWidget) { m_mainWidget->saveSettings(); } } Part::~Part() { disconnect(qApp, &QCoreApplication::aboutToQuit, this, &Part::slotOnShutdown); qCDebug(AKREGATOR_LOG) << "Part::~Part() enter"; // If the widget is destroyed for some reason, KParts::Part will set its // widget property to 0 and then delete itself (and therefore this object). // In this case, it's not safe to do our normal shutdown routine. if (widget() && !m_shuttingDown) { slotOnShutdown(); } qCDebug(AKREGATOR_LOG) << "Part::~Part(): leaving"; } void Part::readProperties(const KConfigGroup &config) { m_backedUpList = false; openStandardFeedList(); if (m_mainWidget) { m_mainWidget->readProperties(config); } } void Part::saveProperties(KConfigGroup &config) { if (m_mainWidget) { slotSaveFeedList(); m_mainWidget->saveProperties(config); } } void Part::exportFile(const QString &str) { exportFile(QUrl(str)); } bool Part::openUrl(const QUrl &url) { setLocalFilePath(url.toLocalFile()); return openFile(); } void Part::openStandardFeedList() { if (!m_standardFeedList.isEmpty()) { openUrl(QUrl::fromLocalFile(m_standardFeedList)); } } bool Part::openFile() { if (m_loadFeedListCommand || m_standardListLoaded) { return true; } QScopedPointer cmd(new LoadFeedListCommand(m_mainWidget)); cmd->setParentWidget(m_mainWidget); cmd->setStorage(Kernel::self()->storage()); cmd->setFileName(localFilePath()); cmd->setDefaultFeedList(createDefaultFeedList()); connect(cmd.data(), &LoadFeedListCommand::result, this, &Part::feedListLoaded); m_loadFeedListCommand = cmd.take(); m_loadFeedListCommand->start(); return true; } bool Part::writeToTextFile(const QString &data, const QString &filename) const { QSaveFile file(filename); if (!file.open(QIODevice::WriteOnly)) { return false; } QTextStream stream(&file); stream.setCodec("UTF-8"); stream << data << endl; return file.commit(); } void Part::feedListLoaded(const QSharedPointer &list) { Q_ASSERT(!m_standardListLoaded); if (!m_mainWidget) { return; } m_mainWidget->setFeedList(list); m_standardListLoaded = list != nullptr; if (Settings::markAllFeedsReadOnStartup()) { m_mainWidget->slotMarkAllFeedsRead(); } if (m_standardListLoaded) { QTimer::singleShot(0, this, &Part::flushAddFeedRequests); } if (Settings::fetchOnStartup()) { m_mainWidget->slotFetchAllFeeds(); } } void Part::flushAddFeedRequests() { if (!m_mainWidget) { return; } for (const AddFeedRequest &i : qAsConst(m_requests)) { for (const QString &j : qAsConst(i.urls)) { m_mainWidget->addFeedToGroup(j, i.group); } NotificationManager::self()->slotNotifyFeeds(i.urls); } m_requests.clear(); } void Part::slotSaveFeedList() { // don't save to the standard feed list, when it wasn't completely loaded before if (!m_standardListLoaded) { return; } // the first time we overwrite the feed list, we create a backup if (!m_backedUpList) { const QString backup = localFilePath() + QLatin1Char('~'); if (QFile::exists(backup)) { QFile::remove(backup); } if (QFile::copy(localFilePath(), backup)) { m_backedUpList = true; } } const QString xml = m_mainWidget->feedListToOPML().toString(); if (xml.isEmpty()) { return; } m_storage->storeFeedList(xml); if (writeToTextFile(xml, localFilePath())) { return; } KMessageBox::error(m_mainWidget, i18n("Access denied: Cannot save feed list to %1. Please check your permissions.", localFilePath()), i18n("Write Error")); } bool Part::isTrayIconEnabled() const { return Settings::showTrayIcon(); } void Part::importFile(const QUrl &url) { QString filename; QTemporaryFile tempFile; if (url.isLocalFile()) { filename = url.toLocalFile(); } else { if (!tempFile.open()) { return; } filename = tempFile.fileName(); auto job = KIO::file_copy(url, QUrl::fromLocalFile(filename), -1, KIO::Overwrite | KIO::HideProgressInfo); KJobWidgets::setWindow(job, m_mainWidget); if (!job->exec()) { KMessageBox::error(m_mainWidget, job->errorString()); return; } } QFile file(filename); if (file.open(QIODevice::ReadOnly)) { // Read OPML feeds list and build QDom tree. QDomDocument doc; if (doc.setContent(file.readAll())) { m_mainWidget->importFeedList(doc); } else { KMessageBox::error(m_mainWidget, i18n("Could not import the file %1 (no valid OPML)", filename), i18n("OPML Parsing Error")); } } else { KMessageBox::error(m_mainWidget, i18n("The file %1 could not be read, check if it exists or if it is readable for the current user.", filename), i18n("Read Error")); } } void Part::exportFile(const QUrl &url) { if (url.isLocalFile()) { const QString fname = url.toLocalFile(); if (!writeToTextFile(m_mainWidget->feedListToOPML().toString(), fname)) { KMessageBox::error(m_mainWidget, i18n("Access denied: cannot write to file %1. Please check your permissions.", fname), i18n("Write Error")); } return; } else { auto job = KIO::storedPut(m_mainWidget->feedListToOPML().toString().toUtf8(), url, -1); KJobWidgets::setWindow(job, m_mainWidget); if (!job->exec()) { KMessageBox::error(m_mainWidget, job->errorString()); } } } void Part::fileImport() { const QString filters = i18n("OPML Outlines (*.opml *.xml);;All Files (*)"); const QUrl url = QFileDialog::getOpenFileUrl(m_mainWidget, QString(), QUrl(), filters); if (!url.isEmpty()) { importFile(url); } } void Part::fileExport() { const QString filters = i18n("OPML Outlines (*.opml *.xml);;All Files (*)"); const QUrl url = QFileDialog::getSaveFileUrl(m_mainWidget, QString(), QUrl(), filters); if (!url.isEmpty()) { exportFile(url); } } void Part::fetchAllFeeds() { m_mainWidget->slotFetchAllFeeds(); } void Part::fetchFeedUrl(const QString &s) { qCDebug(AKREGATOR_LOG) << "fetchFeedURL==" << s; } void Part::addFeedsToGroup(const QStringList &urls, const QString &group) { AddFeedRequest req; req.group = group; req.urls = urls; m_requests.append(req); if (m_standardListLoaded) { flushAddFeedRequests(); } } void Part::addFeed() { m_mainWidget->slotFeedAdd(); } void Part::showNotificationOptions() { const Akregator::AboutData about; KNotifyConfigWidget::configure(m_mainWidget, about.productName()); } void Part::showOptions() { saveSettings(); if (!m_dialog) { m_dialog = new KCMultiDialog(m_mainWidget); connect(m_dialog, qOverload<>(&KCMultiDialog::configCommitted), this, &Part::slotSettingsChanged); if (TrayIcon::getInstance()) { connect(m_dialog, qOverload<>(&KCMultiDialog::configCommitted), TrayIcon::getInstance(), &TrayIcon::settingsChanged); } m_dialog->addModule(QStringLiteral("akregator_config_general")); m_dialog->addModule(QStringLiteral("akregator_config_appearance")); m_dialog->addModule(QStringLiteral("akregator_config_archive")); m_dialog->addModule(QStringLiteral("akregator_config_browser")); m_dialog->addModule(QStringLiteral("akregator_config_advanced")); m_dialog->addModule(QStringLiteral("akregator_config_plugins")); } m_dialog->show(); m_dialog->raise(); } void Part::initFonts() { QStringList fonts = Settings::fonts(); if (fonts.isEmpty()) { fonts.append(QFontDatabase::systemFont(QFontDatabase::GeneralFont).family()); fonts.append(QFontDatabase::systemFont(QFontDatabase::FixedFont).family()); fonts.append(QFontDatabase::systemFont(QFontDatabase::GeneralFont).family()); fonts.append(QFontDatabase::systemFont(QFontDatabase::GeneralFont).family()); fonts.append(QStringLiteral("0")); } Settings::setFonts(fonts); if (Settings::standardFont().isEmpty()) { Settings::setStandardFont(fonts[0]); } if (Settings::fixedFont().isEmpty()) { Settings::setFixedFont(fonts[1]); } if (Settings::sansSerifFont().isEmpty()) { Settings::setSansSerifFont(fonts[2]); } if (Settings::serifFont().isEmpty()) { Settings::setSerifFont(fonts[3]); } //TODO add CursiveFont, FantasyFont KConfigGroup conf(Settings::self()->config(), "HTML Settings"); KConfig _konq(QStringLiteral("konquerorrc"), KConfig::NoGlobals); KConfigGroup konq(&_konq, "HTML Settings"); if (!conf.hasKey("MinimumFontSize")) { int minfs; if (konq.hasKey("MinimumFontSize")) { minfs = konq.readEntry("MinimumFontSize", 8); } else { minfs = std::max(QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize() - 2, 4); } Settings::setMinimumFontSize(minfs); } if (!conf.hasKey("MediumFontSize")) { int medfs; if (konq.hasKey("MediumFontSize")) { medfs = konq.readEntry("MediumFontSize", 12); } else { medfs = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); } Settings::setMediumFontSize(medfs); } QWebEngineSettings::defaultSettings()->setFontFamily(QWebEngineSettings::StandardFont, Settings::standardFont()); QWebEngineSettings::defaultSettings()->setFontFamily(QWebEngineSettings::FixedFont, Settings::fixedFont()); QWebEngineSettings::defaultSettings()->setFontFamily(QWebEngineSettings::SerifFont, Settings::serifFont()); QWebEngineSettings::defaultSettings()->setFontFamily(QWebEngineSettings::SansSerifFont, Settings::sansSerifFont()); QWebEngineSettings::defaultSettings()->setFontSize(QWebEngineSettings::MinimumFontSize, Settings::minimumFontSize()); QWebEngineSettings::defaultSettings()->setFontSize(QWebEngineSettings::DefaultFontSize, Settings::mediumFontSize()); } bool Part::handleCommandLine(const QStringList &args) { QCommandLineParser parser; akregator_options(&parser); parser.process(args); const QString addFeedGroup = parser.isSet(QStringLiteral("group")) ? parser.value(QStringLiteral("group")) : i18n("Imported Folder"); QStringList feedsToAdd = parser.values(QStringLiteral("addfeed")); if (feedsToAdd.isEmpty() && !parser.positionalArguments().isEmpty()) { const auto positionalArguments = parser.positionalArguments(); for (QString url : positionalArguments) { const QUrl tempUrl = QUrl::fromUserInput(url); if (tempUrl.isLocalFile()) { const QString tempLocalFile = tempUrl.toLocalFile(); if (tempLocalFile.startsWith(QDir::tempPath())) { const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/akregator/data/"); QDir().mkpath(path); QFile f(tempLocalFile); const QString newRssFileName = path + QFileInfo(f).fileName(); if (!f.copy(newRssFileName)) { qCWarning(AKREGATOR_LOG) << "Impossible to copy in local folder" << newRssFileName; } else { url = newRssFileName; } } } feedsToAdd.append(url); } } if (!feedsToAdd.isEmpty()) { addFeedsToGroup(feedsToAdd, addFeedGroup); } return true; } void Part::clearCrashProperties() { if (!m_doCrashSave) { return; } KConfig config(QStringLiteral("crashed"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation); KConfigGroup configGroup(&config, "Part"); configGroup.writeEntry("crashed", false); } void Part::saveCrashProperties() { if (!m_doCrashSave) { return; } KConfig config(QStringLiteral("crashed"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation); KConfigGroup configGroup(&config, "Part"); configGroup.deleteGroup(); configGroup.writeEntry("crashed", true); saveProperties(configGroup); } void Part::slotAutoSave() { saveCrashProperties(); } void Part::autoSaveProperties() { KConfig config(QStringLiteral("autosaved"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation); KConfigGroup configGroup(&config, "Part"); configGroup.deleteGroup(); saveProperties(configGroup); clearCrashProperties(); } void Part::autoReadProperties() { if (qGuiApp->isSessionRestored()) { return; } KConfig config(QStringLiteral("autosaved"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation); KConfigGroup configGroup(&config, "Part"); readProperties(configGroup); } void Part::slotRestoreSession(Akregator::CrashWidget::CrashAction type) { switch (type) { case Akregator::CrashWidget::RestoreSession: { KConfig config(QStringLiteral("crashed"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation); KConfigGroup configGroup(&config, "Part"); readProperties(configGroup); clearCrashProperties(); break; } case Akregator::CrashWidget::NotRestoreSession: clearCrashProperties(); break; case Akregator::CrashWidget::AskMeLater: break; } m_doCrashSave = true; } } // namespace Akregator #include "akregator_part.moc" diff --git a/src/akregratormigrateapplication.cpp b/src/akregratormigrateapplication.cpp index 6e85b25d..7d1aab23 100644 --- a/src/akregratormigrateapplication.cpp +++ b/src/akregratormigrateapplication.cpp @@ -1,70 +1,70 @@ /* Copyright (C) 2015-2019 Montel Laurent This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akregratormigrateapplication.h" -#include +#include AkregratorMigrateApplication::AkregratorMigrateApplication() { initializeMigrator(); } void AkregratorMigrateApplication::migrate() { // Migrate to xdg. Kdelibs4ConfigMigrator migrate(QStringLiteral("akregator")); migrate.setConfigFiles(QStringList() << QStringLiteral("akregatorrc") << QStringLiteral("akregator.notifyrc")); migrate.setUiFiles(QStringList() << QStringLiteral("akregator_shell.rc") << QStringLiteral("akregator_part.rc") << QStringLiteral("akregator_sharemicroblog_plugin.rc") << QStringLiteral("akregator_onlinesync_plugin.rc")); migrate.migrate(); // Migrate folders and files. if (mMigrator.checkIfNecessary()) { mMigrator.start(); } } void AkregratorMigrateApplication::initializeMigrator() { const int currentVersion = 2; mMigrator.setApplicationName(QStringLiteral("akregator")); mMigrator.setConfigFileName(QStringLiteral("akregatorrc")); mMigrator.setCurrentConfigVersion(currentVersion); // To migrate we need a version > currentVersion const int initialVersion = currentVersion + 1; // Database PimCommon::MigrateFileInfo migrateInfoArchive; migrateInfoArchive.setFolder(true); migrateInfoArchive.setType(QStringLiteral("data")); migrateInfoArchive.setPath(QStringLiteral("akregator/Archive/")); migrateInfoArchive.setVersion(initialVersion); mMigrator.insertMigrateInfo(migrateInfoArchive); PimCommon::MigrateFileInfo migrateInfoData; migrateInfoData.setFolder(true); migrateInfoData.setType(QStringLiteral("data")); migrateInfoData.setPath(QStringLiteral("akregator/data/")); migrateInfoData.setVersion(initialVersion); mMigrator.insertMigrateInfo(migrateInfoData); //TODO } diff --git a/src/articlematcher.cpp b/src/articlematcher.cpp index 69118530..ec8ff364 100644 --- a/src/articlematcher.cpp +++ b/src/articlematcher.cpp @@ -1,352 +1,352 @@ /* * articlematcher.cpp * * Copyright (c) 2004, 2005 Frerich Raabe * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "articlematcher.h" #include "article.h" #include "types.h" #include #include -#include +#include #include "akregator_debug.h" #include namespace Akregator { namespace Filters { AbstractMatcher::AbstractMatcher() { } AbstractMatcher::~AbstractMatcher() { } QString Criterion::subjectToString(Subject subj) { switch (subj) { case Title: return QStringLiteral("Title"); case Link: return QStringLiteral("Link"); case Description: return QStringLiteral("Description"); case Status: return QStringLiteral("Status"); case KeepFlag: return QStringLiteral("KeepFlag"); case Author: return QStringLiteral("Author"); } return {}; } Criterion::Subject Criterion::stringToSubject(const QString &subjStr) { if (subjStr == QLatin1String("Title")) { return Title; } else if (subjStr == QLatin1String("Link")) { return Link; } else if (subjStr == QLatin1String("Description")) { return Description; } else if (subjStr == QLatin1String("Status")) { return Status; } else if (subjStr == QLatin1String("KeepFlag")) { return KeepFlag; } else if (subjStr == QLatin1String("Author")) { return Author; } // hopefully never reached return Description; } QString Criterion::predicateToString(Predicate pred) { switch (pred) { case Contains: return QStringLiteral("Contains"); case Equals: return QStringLiteral("Equals"); case Matches: return QStringLiteral("Matches"); case Negation: return QStringLiteral("Negation"); } return {}; } Criterion::Predicate Criterion::stringToPredicate(const QString &predStr) { if (predStr == QLatin1String("Contains")) { return Contains; } else if (predStr == QLatin1String("Equals")) { return Equals; } else if (predStr == QLatin1String("Matches")) { return Matches; } else if (predStr == QLatin1String("Negation")) { return Negation; } // hopefully never reached return Contains; } Criterion::Criterion() { } Criterion::Criterion(Subject subject, Predicate predicate, const QVariant &object) : m_subject(subject) , m_predicate(predicate) , m_object(object) { } void Criterion::writeConfig(KConfigGroup *config) const { config->writeEntry(QStringLiteral("subject"), subjectToString(m_subject)); config->writeEntry(QStringLiteral("predicate"), predicateToString(m_predicate)); config->writeEntry(QStringLiteral("objectType"), QString::fromLatin1(m_object.typeName())); config->writeEntry(QStringLiteral("objectValue"), m_object); } void Criterion::readConfig(KConfigGroup *config) { m_subject = stringToSubject(config->readEntry(QStringLiteral("subject"), QString())); m_predicate = stringToPredicate(config->readEntry(QStringLiteral("predicate"), QString())); QVariant::Type type = QVariant::nameToType(config->readEntry(QStringLiteral("objType"), QString()).toLatin1().constData()); if (type != QVariant::Invalid) { m_object = config->readEntry(QStringLiteral("objectValue"), QVariant(type)); } } bool Criterion::satisfiedBy(const Article &article) const { if (article.isNull()) { return false; } QVariant concreteSubject; switch (m_subject) { case Title: concreteSubject = QVariant(article.title()); break; case Description: concreteSubject = QVariant(article.description()); break; case Link: // ### Maybe use prettyUrl here? concreteSubject = QVariant(article.link().url()); break; case Status: concreteSubject = QVariant(article.status()); break; case KeepFlag: concreteSubject = QVariant(article.keep()); break; case Author: concreteSubject = QVariant(article.authorName()); } bool satisfied = false; const Predicate predicateType = static_cast(m_predicate & ~Negation); QString subjectType = QLatin1String(concreteSubject.typeName()); switch (predicateType) { case Contains: satisfied = concreteSubject.toString().indexOf(m_object.toString(), 0, Qt::CaseInsensitive) != -1; break; case Equals: if (subjectType == QLatin1String("int")) { satisfied = concreteSubject.toInt() == m_object.toInt(); } else { satisfied = concreteSubject.toString() == m_object.toString(); } break; case Matches: satisfied = QRegExp(m_object.toString()).indexIn(concreteSubject.toString()) != -1; break; default: qCDebug(AKREGATOR_LOG) << "Internal inconsistency; predicateType should never be Negation"; break; } if (m_predicate & Negation) { satisfied = !satisfied; } return satisfied; } Criterion::Subject Criterion::subject() const { return m_subject; } Criterion::Predicate Criterion::predicate() const { return m_predicate; } QVariant Criterion::object() const { return m_object; } ArticleMatcher::ArticleMatcher() : m_association(None) { } ArticleMatcher::~ArticleMatcher() { } ArticleMatcher::ArticleMatcher(const QVector &criteria, Association assoc) : m_criteria(criteria) , m_association(assoc) { } bool ArticleMatcher::matches(const Article &a) const { switch (m_association) { case LogicalOr: return anyCriterionMatches(a); case LogicalAnd: return allCriteriaMatch(a); default: break; } return true; } void ArticleMatcher::writeConfig(KConfigGroup *config) const { config->writeEntry(QStringLiteral("matcherAssociation"), associationToString(m_association)); config->writeEntry(QStringLiteral("matcherCriteriaCount"), m_criteria.count()); QString criterionGroupPrefix = config->name() + QLatin1String("_Criterion"); const int criteriaSize(m_criteria.size()); for (int index = 0; index < criteriaSize; ++index) { *config = KConfigGroup(config->config(), criterionGroupPrefix + QString::number(index)); m_criteria.at(index).writeConfig(config); } } void ArticleMatcher::readConfig(KConfigGroup *config) { m_criteria.clear(); m_association = stringToAssociation(config->readEntry(QStringLiteral("matcherAssociation"), QString())); const int count = config->readEntry(QStringLiteral("matcherCriteriaCount"), 0); const QString criterionGroupPrefix = config->name() + QLatin1String("_Criterion"); for (int i = 0; i < count; ++i) { Criterion c; *config = KConfigGroup(config->config(), criterionGroupPrefix + QString::number(i)); c.readConfig(config); m_criteria.append(c); } } bool ArticleMatcher::operator==(const AbstractMatcher &other) const { AbstractMatcher *ptr = const_cast(&other); ArticleMatcher *o = dynamic_cast(ptr); if (!o) { return false; } else { return m_association == o->m_association && m_criteria == o->m_criteria; } } bool ArticleMatcher::operator!=(const AbstractMatcher &other) const { return !(*this == other); } bool ArticleMatcher::anyCriterionMatches(const Article &a) const { if (m_criteria.isEmpty()) { return true; } const int criteriaSize(m_criteria.size()); for (int index = 0; index < criteriaSize; ++index) { if (m_criteria.at(index).satisfiedBy(a)) { return true; } } return false; } bool ArticleMatcher::allCriteriaMatch(const Article &a) const { if (m_criteria.isEmpty()) { return true; } const int criteriaSize(m_criteria.size()); for (int index = 0; index < criteriaSize; ++index) { if (!m_criteria.at(index).satisfiedBy(a)) { return false; } } return true; } ArticleMatcher::Association ArticleMatcher::stringToAssociation(const QString &assocStr) { if (assocStr == QLatin1String("LogicalAnd")) { return LogicalAnd; } else if (assocStr == QLatin1String("LogicalOr")) { return LogicalOr; } else { return None; } } QString ArticleMatcher::associationToString(Association association) { switch (association) { case LogicalAnd: return QStringLiteral("LogicalAnd"); case LogicalOr: return QStringLiteral("LogicalOr"); default: return QStringLiteral("None"); } } } //namespace Filters } //namespace Akregator diff --git a/src/articleviewerwidget.cpp b/src/articleviewerwidget.cpp index 56ee1e17..6a85bba6 100644 --- a/src/articleviewerwidget.cpp +++ b/src/articleviewerwidget.cpp @@ -1,420 +1,420 @@ /* This file is part of Akregator. Copyright (C) 2004 Teemu Rytilahti 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "articleviewerwidget.h" #include "akregatorconfig.h" #include "aboutdata.h" #include "actionmanager.h" #include "actions.h" #include "article.h" #include "articleformatter.h" #include "articlejobs.h" #include "articlematcher.h" #include "feed.h" #include "folder.h" #include "treenode.h" #include "utils.h" #include "openurlrequest.h" #include "akregator_debug.h" #include "akregator-version.h" -#include -#include -#include -#include +#include +#include +#include +#include -#include -#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include using namespace Akregator; using namespace Akregator::Filters; ArticleViewerWidget::ArticleViewerWidget(const QString &grantleeDirectory, KActionCollection *ac, QWidget *parent) : QWidget(parent) , m_node(nullptr) , m_viewMode(NormalView) , m_articleViewerWidgetNg(new Akregator::ArticleViewerWebEngineWidgetNg(ac, this)) , m_grantleeDirectory(grantleeDirectory) { QGridLayout *layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_articleViewerWidgetNg); m_articleHtmlWriter = new Akregator::ArticleHtmlWebEngineWriter(m_articleViewerWidgetNg->articleViewerNg(), this); connect(m_articleViewerWidgetNg->articleViewerNg(), &ArticleViewerWebEngine::signalOpenUrlRequest, this, &ArticleViewerWidget::signalOpenUrlRequest); connect(m_articleViewerWidgetNg->articleViewerNg(), &ArticleViewerWebEngine::showStatusBarMessage, this, &ArticleViewerWidget::showStatusBarMessage); } ArticleViewerWidget::~ArticleViewerWidget() { } QSharedPointer ArticleViewerWidget::normalViewFormatter() { if (!m_normalViewFormatter.data()) { m_normalViewFormatter = QSharedPointer(new DefaultNormalViewFormatter(m_grantleeDirectory, m_articleViewerWidgetNg->articleViewerNg())); } return m_normalViewFormatter; } QSharedPointer ArticleViewerWidget::combinedViewFormatter() { if (!m_combinedViewFormatter.data()) { m_combinedViewFormatter = QSharedPointer(new DefaultCombinedViewFormatter(m_grantleeDirectory, m_articleViewerWidgetNg->articleViewerNg())); } return m_combinedViewFormatter; } void ArticleViewerWidget::slotZoomChangeInFrame(qreal value) { m_articleViewerWidgetNg->articleViewerNg()->setZoomFactor(value); } void ArticleViewerWidget::slotCopy() { m_articleViewerWidgetNg->articleViewerNg()->slotCopy(); } void ArticleViewerWidget::slotSelectionChanged() { ActionManager::getInstance()->action(QStringLiteral("viewer_copy"))->setEnabled(!m_articleViewerWidgetNg->articleViewerNg()->selectedText().isEmpty()); } void ArticleViewerWidget::slotPrint() { m_articleViewerWidgetNg->slotPrint(); } void ArticleViewerWidget::slotPrintPreview() { m_articleViewerWidgetNg->slotPrintPreview(); } void ArticleViewerWidget::connectToNode(TreeNode *node) { if (node) { if (m_viewMode == CombinedView) { connect(node, &TreeNode::signalChanged, this, &ArticleViewerWidget::slotUpdateCombinedView); connect(node, &TreeNode::signalArticlesAdded, this, &ArticleViewerWidget::slotArticlesAdded); connect(node, &TreeNode::signalArticlesRemoved, this, &ArticleViewerWidget::slotArticlesRemoved); connect(node, &TreeNode::signalArticlesUpdated, this, &ArticleViewerWidget::slotArticlesUpdated); } else if (m_viewMode == SummaryView) { connect(node, &TreeNode::signalChanged, this, &ArticleViewerWidget::slotShowSummary); } connect(node, &TreeNode::signalDestroyed, this, &ArticleViewerWidget::slotClear); } } void ArticleViewerWidget::disconnectFromNode(TreeNode *node) { if (node) { node->disconnect(this); } } void ArticleViewerWidget::renderContent(const QString &text) { m_currentText = text; reload(); } void ArticleViewerWidget::beginWriting() { m_articleHtmlWriter->begin(); } void ArticleViewerWidget::endWriting() { m_articleHtmlWriter->end(); } void ArticleViewerWidget::slotShowSummary(TreeNode *node) { m_viewMode = SummaryView; if (!node) { slotClear(); return; } if (node != m_node) { disconnectFromNode(m_node); connectToNode(node); m_node = node; } const QString summary = normalViewFormatter()->formatSummary(node); m_link.clear(); renderContent(summary); setArticleActionsEnabled(false); } void ArticleViewerWidget::showArticle(const Akregator::Article &article) { if (article.isNull() || article.isDeleted()) { slotClear(); return; } const QUrl xmlUrl = QUrl(article.feed()->xmlUrl()); qCDebug(AKREGATOR_LOG) << "showing Article - xmlUrl:" << xmlUrl; m_articleHtmlWriter->setBaseUrl(xmlUrl); m_viewMode = NormalView; disconnectFromNode(m_node); m_article = article; m_node = nullptr; m_link = article.link(); if (article.feed()->loadLinkedWebsite()) { openUrl(article.link()); } else { renderContent(normalViewFormatter()->formatArticles(QVector() << article, ArticleFormatter::ShowIcon)); } setArticleActionsEnabled(true); } bool ArticleViewerWidget::openUrl(const QUrl &url) { if (!m_article.isNull() && m_article.feed()->loadLinkedWebsite()) { m_articleViewerWidgetNg->articleViewerNg()->load(url); } else { reload(); } return true; } void ArticleViewerWidget::setFilters(const std::vector< QSharedPointer > &filters) { if (filters == m_filters) { return; } m_filters = filters; slotUpdateCombinedView(); } void ArticleViewerWidget::slotUpdateCombinedView() { if (m_viewMode != CombinedView) { return; } if (!m_node) { return slotClear(); } m_articleViewerWidgetNg->saveCurrentPosition(); QString text; int num = 0; QElapsedTimer spent; spent.start(); const std::vector< QSharedPointer >::const_iterator filterEnd = m_filters.cend(); QVector
articles; for (const Article &i : qAsConst(m_articles)) { if (i.isDeleted()) { continue; } auto func = [i](const QSharedPointer &matcher) -> bool { return !matcher->matches(i); }; if (std::find_if(m_filters.cbegin(), filterEnd, func) != filterEnd) { continue; } articles << i; ++num; } text = combinedViewFormatter()->formatArticles(articles, ArticleFormatter::NoIcon); qCDebug(AKREGATOR_LOG) << "Combined view rendering: (" << num << " articles):" << "generating HTML:" << spent.elapsed() << "ms"; renderContent(text); qCDebug(AKREGATOR_LOG) << "HTML rendering:" << spent.elapsed() << "ms"; } void ArticleViewerWidget::slotArticlesUpdated(TreeNode * /*node*/, const QVector
& /*list*/) { if (m_viewMode == CombinedView) { //TODO slotUpdateCombinedView(); } } void ArticleViewerWidget::slotArticlesAdded(TreeNode * /*node*/, const QVector
&list) { if (m_viewMode == CombinedView) { //TODO sort list, then merge m_articles << list; std::sort(m_articles.begin(), m_articles.end()); slotUpdateCombinedView(); } } void ArticleViewerWidget::slotArticlesRemoved(TreeNode * /*node*/, const QVector
&list) { Q_UNUSED(list) if (m_viewMode == CombinedView) { //TODO slotUpdateCombinedView(); } } void ArticleViewerWidget::slotClear() { disconnectFromNode(m_node); m_node = nullptr; m_article = Article(); m_articles.clear(); renderContent(QString()); } void ArticleViewerWidget::showNode(TreeNode *node) { m_viewMode = CombinedView; if (node != m_node) { disconnectFromNode(m_node); } connectToNode(node); m_articles.clear(); m_article = Article(); m_node = node; delete m_listJob; m_listJob = node->createListJob(); connect(m_listJob.data(), &ArticleListJob::finished, this, &ArticleViewerWidget::slotArticlesListed); m_listJob->start(); slotUpdateCombinedView(); } qreal ArticleViewerWidget::zoomFactor() const { return m_articleViewerWidgetNg->articleViewerNg()->zoomFactor(); } void ArticleViewerWidget::slotArticlesListed(KJob *job) { Q_ASSERT(job); Q_ASSERT(job == m_listJob); TreeNode *node = m_listJob->node(); if (job->error() || !node) { if (!node) { qCWarning(AKREGATOR_LOG) << "Node to be listed is already deleted"; } else { qCWarning(AKREGATOR_LOG) << job->errorText(); } slotUpdateCombinedView(); return; } m_articles = m_listJob->articles(); std::sort(m_articles.begin(), m_articles.end()); if (node && !m_articles.isEmpty()) { m_link = m_articles.first().link(); } else { m_link = QUrl(); } slotUpdateCombinedView(); } void ArticleViewerWidget::keyPressEvent(QKeyEvent *e) { e->ignore(); } void ArticleViewerWidget::updateAfterConfigChanged() { switch (m_viewMode) { case NormalView: if (!m_article.isNull()) { renderContent(normalViewFormatter()->formatArticles(QVector() << m_article, ArticleFormatter::ShowIcon)); } break; case CombinedView: slotUpdateCombinedView(); break; case SummaryView: slotShowSummary(m_node); break; } } void ArticleViewerWidget::reload() { beginWriting(); m_articleHtmlWriter->queue(m_currentText); endWriting(); } QSize ArticleViewerWidget::sizeHint() const { // Increase height a bit so that we can (roughly) read 25 lines of text QSize sh = QWidget::sizeHint(); sh.setHeight(qMax(sh.height(), 25 * fontMetrics().height())); return sh; } void ArticleViewerWidget::displayAboutPage() { m_articleViewerWidgetNg->articleViewerNg()->showAboutPage(); } void ArticleViewerWidget::setArticleActionsEnabled(bool enabled) { ActionManager::getInstance()->setArticleActionsEnabled(enabled); } Akregator::ArticleViewerWebEngineWidgetNg *ArticleViewerWidget::articleViewerWidgetNg() const { return m_articleViewerWidgetNg; } diff --git a/src/feed/feedlist.cpp b/src/feed/feedlist.cpp index 80cb05c5..5a275340 100644 --- a/src/feed/feedlist.cpp +++ b/src/feed/feedlist.cpp @@ -1,631 +1,631 @@ /* This file is part of Akregator. Copyright (C) 2004 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "feedlist.h" #include "storage.h" #include "article.h" #include "feed.h" #include "folder.h" #include "treenode.h" #include "treenodevisitor.h" #include "kernel.h" #include "subscriptionlistjobs.h" #include #include "akregator_debug.h" #include -#include +#include #include #include #include #include using namespace Akregator; class Q_DECL_HIDDEN FeedList::Private { FeedList *const q; public: Private(Backend::Storage *st, FeedList *qq); Akregator::Backend::Storage *storage; QList flatList; Folder *rootNode; QHash idMap; AddNodeVisitor *addNodeVisitor; RemoveNodeVisitor *removeNodeVisitor; QHash > urlMap; mutable int unreadCache; }; class FeedList::AddNodeVisitor : public TreeNodeVisitor { public: AddNodeVisitor(FeedList *list) : m_list(list) { } bool visitFeed(Feed *node) override { m_list->d->idMap.insert(node->id(), node); m_list->d->flatList.append(node); m_list->d->urlMap[node->xmlUrl()].append(node); connect(node, &Feed::fetchStarted, m_list, &FeedList::fetchStarted); connect(node, &Feed::fetched, m_list, &FeedList::fetched); connect(node, &Feed::fetchAborted, m_list, &FeedList::fetchAborted); connect(node, &Feed::fetchError, m_list, &FeedList::fetchError); connect(node, &Feed::fetchDiscovery, m_list, &FeedList::fetchDiscovery); visitTreeNode(node); return true; } void visit2(TreeNode *node, bool preserveID) { m_preserveID = preserveID; TreeNodeVisitor::visit(node); } bool visitTreeNode(TreeNode *node) override { if (!m_preserveID) { node->setId(m_list->generateID()); } m_list->d->idMap[node->id()] = node; m_list->d->flatList.append(node); connect(node, &TreeNode::signalDestroyed, m_list, &FeedList::slotNodeDestroyed); connect(node, &TreeNode::signalChanged, m_list, &FeedList::signalNodeChanged); Q_EMIT m_list->signalNodeAdded(node); return true; } bool visitFolder(Folder *node) override { connect(node, &Folder::signalChildAdded, m_list, &FeedList::slotNodeAdded); connect(node, &Folder::signalAboutToRemoveChild, m_list, &FeedList::signalAboutToRemoveNode); connect(node, &Folder::signalChildRemoved, m_list, &FeedList::slotNodeRemoved); visitTreeNode(node); for (TreeNode *i = node->firstChild(); i && i != node; i = i->next()) { m_list->slotNodeAdded(i); } return true; } private: FeedList *m_list = nullptr; bool m_preserveID = false; }; class FeedList::RemoveNodeVisitor : public TreeNodeVisitor { public: RemoveNodeVisitor(FeedList *list) : m_list(list) { } bool visitFeed(Feed *node) override { visitTreeNode(node); m_list->d->urlMap[node->xmlUrl()].removeAll(node); return true; } bool visitTreeNode(TreeNode *node) override { m_list->d->idMap.remove(node->id()); m_list->d->flatList.removeAll(node); m_list->disconnect(node); return true; } bool visitFolder(Folder *node) override { visitTreeNode(node); return true; } private: FeedList *m_list; }; FeedList::Private::Private(Backend::Storage *st, FeedList *qq) : q(qq) , storage(st) , rootNode(nullptr) , addNodeVisitor(new AddNodeVisitor(q)) , removeNodeVisitor(new RemoveNodeVisitor(q)) , unreadCache(-1) { Q_ASSERT(storage); } FeedList::FeedList(Backend::Storage *storage) : QObject(nullptr) , d(new Private(storage, this)) { Folder *rootNode = new Folder(i18n("All Feeds")); rootNode->setId(1); setRootNode(rootNode); addNode(rootNode, true); } QVector FeedList::feedIds() const { QVector ids; const auto f = feeds(); for (const Feed *const i : f) { ids += i->id(); } return ids; } QVector FeedList::feeds() const { QVector constList; const auto rootNodeFeeds = d->rootNode->feeds(); for (const Feed *const i : rootNodeFeeds) { constList.append(i); } return constList; } QVector FeedList::feeds() { return d->rootNode->feeds(); } QVector FeedList::folders() const { QVector constList; const auto nodeFolders = d->rootNode->folders(); for (const Folder *const i : nodeFolders) { constList.append(i); } return constList; } QVector FeedList::folders() { return d->rootNode->folders(); } void FeedList::addNode(TreeNode *node, bool preserveID) { d->addNodeVisitor->visit2(node, preserveID); } void FeedList::removeNode(TreeNode *node) { d->removeNodeVisitor->visit(node); } void FeedList::parseChildNodes(QDomNode &node, Folder *parent) { QDomElement e = node.toElement(); // try to convert the node to an element. if (!e.isNull()) { //QString title = e.hasAttribute("text") ? e.attribute("text") : e.attribute("title"); if (e.hasAttribute(QStringLiteral("xmlUrl")) || e.hasAttribute(QStringLiteral("xmlurl")) || e.hasAttribute(QStringLiteral("xmlURL"))) { Feed *feed = Feed::fromOPML(e, d->storage); if (feed) { if (!d->urlMap[feed->xmlUrl()].contains(feed)) { d->urlMap[feed->xmlUrl()].append(feed); } parent->appendChild(feed); } } else { Folder *fg = Folder::fromOPML(e); parent->appendChild(fg); if (e.hasChildNodes()) { QDomNode child = e.firstChild(); while (!child.isNull()) { parseChildNodes(child, fg); child = child.nextSibling(); } } } } } bool FeedList::readFromOpml(const QDomDocument &doc) { QDomElement root = doc.documentElement(); qCDebug(AKREGATOR_LOG) << "loading OPML feed" << root.tagName().toLower(); qCDebug(AKREGATOR_LOG) << "measuring startup time: START"; QElapsedTimer spent; spent.start(); if (root.tagName().toLower() != QLatin1String("opml")) { return false; } QDomNode bodyNode = root.firstChild(); while (!bodyNode.isNull() && bodyNode.toElement().tagName().toLower() != QLatin1String("body")) { bodyNode = bodyNode.nextSibling(); } if (bodyNode.isNull()) { qCDebug(AKREGATOR_LOG) << "Failed to acquire body node, markup broken?"; return false; } QDomElement body = bodyNode.toElement(); QDomNode i = body.firstChild(); while (!i.isNull()) { parseChildNodes(i, allFeedsFolder()); i = i.nextSibling(); } for (TreeNode *i = allFeedsFolder()->firstChild(); i && i != allFeedsFolder(); i = i->next()) { if (i->id() == 0) { uint id = generateID(); i->setId(id); d->idMap.insert(id, i); } } qCDebug(AKREGATOR_LOG) << "measuring startup time: STOP," << spent.elapsed() << "ms"; qCDebug(AKREGATOR_LOG) << "Number of articles loaded:" << allFeedsFolder()->totalCount(); return true; } FeedList::~FeedList() { Q_EMIT signalDestroyed(this); setRootNode(nullptr); delete d->addNodeVisitor; delete d->removeNodeVisitor; delete d; } const Feed *FeedList::findByURL(const QString &feedURL) const { if (!d->urlMap.contains(feedURL)) { return nullptr; } const QList &v = d->urlMap[feedURL]; return !v.isEmpty() ? v.front() : nullptr; } Feed *FeedList::findByURL(const QString &feedURL) { if (!d->urlMap.contains(feedURL)) { return nullptr; } const QList &v = d->urlMap[feedURL]; return !v.isEmpty() ? v.front() : nullptr; } const Article FeedList::findArticle(const QString &feedURL, const QString &guid) const { const Feed *feed = findByURL(feedURL); return feed ? feed->findArticle(guid) : Article(); } void FeedList::append(FeedList *list, Folder *parent, TreeNode *after) { if (list == this) { return; } if (!d->flatList.contains(parent)) { parent = allFeedsFolder(); } QList children = list->allFeedsFolder()->children(); QList::ConstIterator end(children.constEnd()); for (QList::ConstIterator it = children.constBegin(); it != end; ++it) { list->allFeedsFolder()->removeChild(*it); parent->insertChild(*it, after); after = *it; } } QDomDocument FeedList::toOpml() const { QDomDocument doc; doc.appendChild(doc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\" encoding=\"UTF-8\""))); QDomElement root = doc.createElement(QStringLiteral("opml")); root.setAttribute(QStringLiteral("version"), QStringLiteral("1.0")); doc.appendChild(root); QDomElement head = doc.createElement(QStringLiteral("head")); root.appendChild(head); QDomElement ti = doc.createElement(QStringLiteral("text")); head.appendChild(ti); QDomElement body = doc.createElement(QStringLiteral("body")); root.appendChild(body); const auto children = allFeedsFolder()->children(); for (const TreeNode *const i : children) { body.appendChild(i->toOPML(body, doc)); } return doc; } const TreeNode *FeedList::findByID(int id) const { return d->idMap[id]; } TreeNode *FeedList::findByID(int id) { return d->idMap[id]; } QList FeedList::findByTitle(const QString &title) const { return allFeedsFolder()->namedChildren(title); } QList FeedList::findByTitle(const QString &title) { return allFeedsFolder()->namedChildren(title); } const Folder *FeedList::allFeedsFolder() const { return d->rootNode; } Folder *FeedList::allFeedsFolder() { return d->rootNode; } bool FeedList::isEmpty() const { return d->rootNode->firstChild() == nullptr; } void FeedList::rootNodeChanged() { Q_ASSERT(d->rootNode); const int newUnread = d->rootNode->unread(); if (newUnread == d->unreadCache) { return; } d->unreadCache = newUnread; Q_EMIT unreadCountChanged(newUnread); } void FeedList::setRootNode(Folder *folder) { if (folder == d->rootNode) { return; } delete d->rootNode; d->rootNode = folder; d->unreadCache = -1; if (d->rootNode) { d->rootNode->setOpen(true); connect(d->rootNode, &Folder::signalChildAdded, this, &FeedList::slotNodeAdded); connect(d->rootNode, &Folder::signalAboutToRemoveChild, this, &FeedList::signalAboutToRemoveNode); connect(d->rootNode, &Folder::signalChildRemoved, this, &FeedList::slotNodeRemoved); connect(d->rootNode, &Folder::signalChanged, this, &FeedList::signalNodeChanged); connect(d->rootNode, &Folder::signalChanged, this, &FeedList::rootNodeChanged); } } int FeedList::generateID() const { return KRandom::random(); } void FeedList::slotNodeAdded(TreeNode *node) { if (!node) { return; } Folder *parent = node->parent(); if (!parent || !d->flatList.contains(parent) || d->flatList.contains(node)) { return; } addNode(node, false); } void FeedList::slotNodeDestroyed(TreeNode *node) { if (!node || !d->flatList.contains(node)) { return; } removeNode(node); } void FeedList::slotNodeRemoved(Folder * /*parent*/, TreeNode *node) { if (!node || !d->flatList.contains(node)) { return; } removeNode(node); Q_EMIT signalNodeRemoved(node); } int FeedList::unread() const { if (d->unreadCache == -1) { d->unreadCache = d->rootNode ? d->rootNode->unread() : 0; } return d->unreadCache; } void FeedList::addToFetchQueue(FetchQueue *qu, bool intervalOnly) { if (d->rootNode) { d->rootNode->slotAddToFetchQueue(qu, intervalOnly); } } KJob *FeedList::createMarkAsReadJob() { return d->rootNode ? d->rootNode->createMarkAsReadJob() : nullptr; } FeedListManagementImpl::FeedListManagementImpl(const QSharedPointer &list) : m_feedList(list) { } void FeedListManagementImpl::setFeedList(const QSharedPointer &list) { m_feedList = list; } static QString path_of_folder(const Folder *fol) { Q_ASSERT(fol); QString path; const Folder *i = fol; while (i) { path = QString::number(i->id()) + QLatin1Char('/') + path; i = i->parent(); } return path; } QStringList FeedListManagementImpl::categories() const { if (!m_feedList) { return QStringList(); } QStringList cats; const auto folders = m_feedList->folders(); for (const Folder *const i : folders) { cats.append(path_of_folder(i)); } return cats; } QStringList FeedListManagementImpl::feeds(const QString &catId) const { if (!m_feedList) { return QStringList(); } const uint lastcatid = catId.split(QLatin1Char('/'), QString::SkipEmptyParts).last().toUInt(); QSet urls; const auto feeds = m_feedList->feeds(); for (const Feed *const i : feeds) { if (lastcatid == i->parent()->id()) { urls.insert(i->xmlUrl()); } } return urls.values(); } void FeedListManagementImpl::addFeed(const QString &url, const QString &catId) { if (!m_feedList) { return; } qCDebug(AKREGATOR_LOG) << "Name:" << url.left(20) << "Cat:" << catId; const uint folder_id = catId.split(QLatin1Char('/'), QString::SkipEmptyParts).last().toUInt(); // Get the folder Folder *m_folder = nullptr; const QVector vector = m_feedList->folders(); for (int i = 0; i < vector.size(); ++i) { if (vector.at(i)->id() == folder_id) { m_folder = vector.at(i); break; } } // Create new feed QScopedPointer new_feedlist(new FeedList(Kernel::self()->storage())); Feed *new_feed = new Feed(Kernel::self()->storage()); new_feed->setXmlUrl(url); // new_feed->setTitle(url); new_feedlist->allFeedsFolder()->appendChild(new_feed); // Get last in the folder TreeNode *m_last = m_folder->childAt(m_folder->totalCount()); // Add the feed m_feedList->append(new_feedlist.data(), m_folder, m_last); } void FeedListManagementImpl::removeFeed(const QString &url, const QString &catId) { qCDebug(AKREGATOR_LOG) << "Name:" << url.left(20) << "Cat:" << catId; uint lastcatid = catId.split(QLatin1Char('/'), QString::SkipEmptyParts).last().toUInt(); const auto feeds = m_feedList->feeds(); for (const Feed *const i : feeds) { if (lastcatid == i->parent()->id()) { if (i->xmlUrl().compare(url) == 0) { qCDebug(AKREGATOR_LOG) << "id:" << i->id(); DeleteSubscriptionJob *job = new DeleteSubscriptionJob; job->setSubscriptionId(i->id()); job->start(); } } } } QString FeedListManagementImpl::getCategoryName(const QString &catId) const { QString catname; if (!m_feedList) { return catname; } const QStringList list = catId.split(QLatin1Char('/'), QString::SkipEmptyParts); for (int i = 0; i < list.size(); ++i) { int index = list.at(i).toInt(); catname += m_feedList->findByID(index)->title() + QLatin1Char('/'); } return catname; } diff --git a/src/frame/frame.cpp b/src/frame/frame.cpp index f9deba3a..685ea6bb 100644 --- a/src/frame/frame.cpp +++ b/src/frame/frame.cpp @@ -1,202 +1,202 @@ /* This file is part of Akregator. Copyright (C) 2004 Sashmit Bhaduri This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "frame.h" #include "utils.h" -#include +#include #include "akregator_debug.h" #include #include using namespace Akregator; Frame::Frame(QWidget *parent) : QWidget(parent) { m_title = i18n("Untitled"); m_state = Idle; m_progress = -1; m_progressItem = nullptr; m_isRemovable = true; m_loading = false; m_id = m_idCounter++; } void Frame::slotSetTitle(const QString &s) { if (m_title != s) { m_title = s; Q_EMIT signalTitleChanged(this, s); } } bool Frame::isLoading() const { return m_loading; } void Frame::slotSetCaption(const QString &s) { if (m_progressItem) { m_progressItem->setLabel(s); } m_caption = s; Q_EMIT signalCaptionChanged(this, s); } void Frame::slotSetStatusText(const QString &s) { m_statusText = s; m_statusText = Akregator::Utils::stripTags(m_statusText); Q_EMIT signalStatusText(this, m_statusText); } void Frame::slotSetProgress(int a) { if (m_progressItem) { m_progressItem->setProgress(static_cast(a)); } m_progress = a; Q_EMIT signalLoadingProgress(this, a); } void Frame::slotSetState(State state) { m_state = state; switch (m_state) { case Frame::Started: Q_EMIT signalStarted(this); break; case Frame::Canceled: Q_EMIT signalCanceled(this, QString()); break; case Frame::Idle: case Frame::Completed: Q_EMIT signalCompleted(this); } } int Frame::m_idCounter = 0; int Frame::id() const { return m_id; } void Frame::setRemovable(bool removable) { m_isRemovable = removable; } bool Frame::isRemovable() const { return m_isRemovable; } Frame::~Frame() { if (m_progressItem) { m_progressItem->setComplete(); } } Frame::State Frame::state() const { return m_state; } QString Frame::title() const { return m_title; } QString Frame::caption() const { return m_caption; } QString Frame::statusText() const { return m_statusText; } QIcon Frame::icon() const { return m_icon; } void Frame::setIcon(const QIcon &icon) { m_icon = icon; } void Frame::slotSetStarted() { m_loading = true; if (m_progressId.isNull() || m_progressId.isEmpty()) { m_progressId = KPIM::ProgressManager::getUniqueID(); } m_progressItem = KPIM::ProgressManager::createProgressItem(m_progressId, title(), QString(), false); m_progressItem->setStatus(i18n("Loading...")); m_state = Started; Q_EMIT signalStarted(this); } void Frame::slotStop() { if (m_loading) { m_loading = false; } } void Frame::slotSetCanceled(const QString &s) { m_loading = false; if (m_progressItem) { m_progressItem->setStatus(i18n("Loading canceled")); m_progressItem->setComplete(); m_progressItem = nullptr; } m_state = Canceled; Q_EMIT signalCanceled(this, s); } void Frame::slotSetCompleted() { m_loading = false; if (m_progressItem) { m_progressItem->setStatus(i18n("Loading completed")); m_progressItem->setComplete(); m_progressItem = nullptr; } m_state = Completed; Q_EMIT signalCompleted(this); } int Frame::progress() const { return m_progress; } diff --git a/src/frame/frame.h b/src/frame/frame.h index 7315e10c..9c67a9e7 100644 --- a/src/frame/frame.h +++ b/src/frame/frame.h @@ -1,150 +1,150 @@ /* This file is part of Akregator. Copyright (C) 2004 Sashmit Bhaduri 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef AKREGATOR_FRAME_H #define AKREGATOR_FRAME_H #include #include #include -#include +#include #include "akregator_export.h" class QUrl; namespace KPIM { class ProgressItem; } namespace Akregator { class OpenUrlRequest; class AKREGATOR_EXPORT Frame : public QWidget { Q_OBJECT public: explicit Frame(QWidget *parent = nullptr); ~Frame() override; enum State { Idle, Started, Completed, Canceled }; virtual qreal zoomFactor() const = 0; /** * Returns whether the frame can be removed from * Akregator (via detach or close tab etc.) Usually * all tabs but the main tab can be removed. * Default is @c true */ bool isRemovable() const; /** * returns the URL of the embedded part */ virtual QUrl url() const = 0; Q_REQUIRED_RESULT QString title() const; Q_REQUIRED_RESULT QString caption() const; Q_REQUIRED_RESULT State state() const; Q_REQUIRED_RESULT int progress() const; Q_REQUIRED_RESULT QString statusText() const; Q_REQUIRED_RESULT QIcon icon() const; void setIcon(const QIcon &icon); Q_REQUIRED_RESULT int id() const; /** * returns whether the embedded part is loading a website. If so, it can be stopped using slotStop() */ virtual bool isLoading() const; virtual bool openUrl(const OpenUrlRequest &request) = 0; /** * Load a frame from a config file for session management. */ virtual void loadConfig(const KConfigGroup & /*config*/, const QString & /*prefix*/) { } /** * Save a frame to a config file for session management. */ virtual bool saveConfig(KConfigGroup & /*config*/, const QString & /*prefix*/) { return false; } public Q_SLOTS: /** reloads the current content, if possible. See also isReloadable(). */ virtual void slotReload() { } virtual void slotStop(); void slotSetStarted(); void slotSetCanceled(const QString &); void slotSetCompleted(); void slotSetState(State); void slotSetProgress(int); void slotSetCaption(const QString &); void slotSetTitle(const QString &); void slotSetStatusText(const QString &); Q_SIGNALS: void signalCaptionChanged(Akregator::Frame *, const QString &); void signalTitleChanged(Akregator::Frame *, const QString &); void signalStarted(Akregator::Frame *); void signalCanceled(Akregator::Frame *, const QString &); void signalCompleted(Akregator::Frame *); void signalLoadingProgress(Akregator::Frame *, int); void signalStatusText(Akregator::Frame *, const QString &); void signalOpenUrlRequest(Akregator::OpenUrlRequest &request); void showStatusBarMessage(const QString &msg); protected: void setRemovable(bool removable); protected: QIcon m_icon; QString m_title; QString m_caption; State m_state; int m_progress; QString m_statusText; QString m_progressId; KPIM::ProgressItem *m_progressItem = nullptr; bool m_isRemovable = false; bool m_loading = false; int m_id; static int m_idCounter; }; } // namespace Akregator #endif // AKREGATOR_FRAME_H diff --git a/src/frame/framemanager.cpp b/src/frame/framemanager.cpp index 3461e239..164deb0e 100644 --- a/src/frame/framemanager.cpp +++ b/src/frame/framemanager.cpp @@ -1,288 +1,288 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "framemanager.h" #include "akregatorconfig.h" #include "frame.h" #include "actionmanager.h" #include "openurlrequest.h" #include #include -#include -#include -#include +#include +#include +#include #include "akregator_debug.h" #include #include #include using namespace Akregator; FrameManager::FrameManager(QObject *parent) : QObject(parent) , m_currentFrame(nullptr) { } FrameManager::~FrameManager() { } Frame *FrameManager::currentFrame() const { return m_currentFrame; } void FrameManager::slotAddFrame(Frame *frame) { m_frames.insert(frame->id(), frame); connect(frame, &Frame::signalCanceled, this, &FrameManager::slotSetCanceled); connect(frame, &Frame::signalStarted, this, &FrameManager::slotSetStarted); connect(frame, &Frame::signalCaptionChanged, this, &FrameManager::slotSetCaption); connect(frame, &Frame::signalLoadingProgress, this, &FrameManager::slotSetProgress); connect(frame, &Frame::signalCompleted, this, &FrameManager::slotSetCompleted); connect(frame, &Frame::signalTitleChanged, this, &FrameManager::slotSetTitle); connect(frame, &Frame::signalStatusText, this, &FrameManager::slotSetStatusText); connect(frame, &Frame::signalOpenUrlRequest, this, [this](OpenUrlRequest &request) { slotOpenUrlRequest(request); }); //setPartGuiActive(frame->part(), false); Q_EMIT signalFrameAdded(frame); if (m_frames.count() == 1) { slotChangeFrame(frame->id()); } } void FrameManager::slotRemoveFrame(int id) { Frame *frame = m_frames.value(id); if (!frame || !frame->isRemovable()) { return; } frame->disconnect(this); if (m_currentFrame == frame) { slotChangeFrame(-1); } m_frames.insert(id, nullptr); m_frames.remove(id); Q_EMIT signalFrameRemoved(id); delete frame; } Frame *FrameManager::findFrameById(int id) const { return m_frames.value(id); } void FrameManager::slotChangeFrame(int frameId) { Frame *frame = m_frames.value(frameId); if (frame == m_currentFrame) { return; } Frame *oldFrame = m_currentFrame; m_currentFrame = frame; //if (oldFrame) { // setPartGuiActive(oldFrame->part(), false); //} if (frame) { //setPartGuiActive(frame->part(), true); // TODO: handle removable flag switch (frame->state()) { case Frame::Started: Q_EMIT signalStarted(); break; case Frame::Canceled: Q_EMIT signalCanceled(QString()); break; case Frame::Idle: case Frame::Completed: Q_EMIT signalCompleted(); } Q_EMIT signalCaptionChanged(frame->caption()); Q_EMIT signalTitleChanged(frame->title()); Q_EMIT signalLoadingProgress(frame->progress()); Q_EMIT signalStatusText(frame->statusText()); } else { Q_EMIT signalCompleted(); Q_EMIT signalCaptionChanged(QString()); Q_EMIT signalTitleChanged(QString()); Q_EMIT signalLoadingProgress(100); Q_EMIT signalStatusText(QString()); } Q_EMIT signalCurrentFrameChanged(oldFrame, frame); } void FrameManager::slotSetStarted(Frame *frame) { if (frame == m_currentFrame) { Q_EMIT signalStarted(); } } void FrameManager::slotSetCanceled(Frame *frame, const QString &reason) { if (frame == m_currentFrame) { Q_EMIT signalCanceled(reason); } } void FrameManager::slotSetCompleted(Frame *frame) { if (frame == m_currentFrame) { Q_EMIT signalCompleted(); } } void FrameManager::slotSetProgress(Frame *frame, int progress) { if (frame == m_currentFrame) { Q_EMIT signalLoadingProgress(progress); } } void FrameManager::slotSetCaption(Frame *frame, const QString &caption) { if (frame == m_currentFrame) { Q_EMIT signalCaptionChanged(caption); } } void FrameManager::slotSetTitle(Frame *frame, const QString &title) { if (frame == m_currentFrame) { Q_EMIT signalTitleChanged(title); } } void FrameManager::slotSetStatusText(Frame *frame, const QString &statusText) { if (frame == m_currentFrame) { Q_EMIT signalStatusText(statusText); } } void FrameManager::openUrl(OpenUrlRequest &request) { if (request.browserArgs().newTab() || request.browserArgs().forcesNewWindow() || request.options() == OpenUrlRequest::NewTab || (m_currentFrame->id() == 0)) { int newFrameId = -1; Q_EMIT signalRequestNewFrame(newFrameId); request.setFrameId(newFrameId); } else { request.setFrameId(m_currentFrame->id()); } if (m_frames.contains(request.frameId())) { Frame *frame = m_frames.value(request.frameId()); if (frame->openUrl(request)) { request.setWasHandled(true); } } if (!request.openInBackground()) { Q_EMIT signalSelectFrame(request.frameId()); } } void FrameManager::openInExternalBrowser(const OpenUrlRequest &request) { QUrl url = request.url(); if (!url.isValid()) { return; } if (!Settings::externalBrowserUseKdeDefault()) { QHash map; map.insert(QLatin1Char('u'), url.url()); const QString cmd = KMacroExpander::expandMacrosShellQuote(Settings::externalBrowserCustomCommand(), map); const QStringList args = KShell::splitArgs(cmd); if (!args.isEmpty()) { KProcess::startDetached(args); return; } } if (request.args().mimeType().isEmpty()) { QDesktopServices::openUrl(url); } else { KRun::RunFlags flag = {}; KRun::runUrl(url, request.args().mimeType(), nullptr /*window*/, flag); } } void FrameManager::slotOpenUrlRequest(OpenUrlRequest &request, bool useOpenInBackgroundSetting) { qCDebug(AKREGATOR_LOG) << "FrameManager::slotOpenUrlRequest():" << request.debugInfo(); if (request.options() == OpenUrlRequest::ExternalBrowser) { openInExternalBrowser(request); return; } if (useOpenInBackgroundSetting) { // Honour user's preference for foreground/background tabs if (request.options() == OpenUrlRequest::NewTab || request.browserArgs().newTab()) { request.setOpenInBackground(Settings::lMBBehaviour() == Settings::EnumLMBBehaviour::OpenInBackground); } } openUrl(request); } void FrameManager::saveProperties(KConfigGroup &config) { //write children QStringList strlst; QHash::const_iterator i; QHash::const_iterator end(m_frames.constEnd()); for (i = m_frames.constBegin(); i != end; ++i) { // No need to save the main frame Frame *currentFrame = i.value(); if (currentFrame && qobject_cast(currentFrame)) { QString newPrefix = QLatin1Char('T') + QString::number(i.key()); if (currentFrame->saveConfig(config, newPrefix + QLatin1Char('_'))) { strlst.append(newPrefix); if (currentFrame == m_currentFrame) { config.writeEntry(QStringLiteral("CurrentTab"), newPrefix); } } } } config.writeEntry(QStringLiteral("Children"), strlst); } diff --git a/src/mainwidget.cpp b/src/mainwidget.cpp index 0e3925d5..835e6eee 100644 --- a/src/mainwidget.cpp +++ b/src/mainwidget.cpp @@ -1,1341 +1,1341 @@ /* This file is part of Akregator. Copyright (C) 2004 Stanislav Karchebny 2004 Sashmit Bhaduri 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "mainwidget.h" #include "utils.h" #include "actionmanagerimpl.h" #include "addfeeddialog.h" #include "articlelistview.h" #include "articleviewerwidget.h" #include "abstractselectioncontroller.h" #include "articlejobs.h" #include "articlematcher.h" #include "akregatorconfig.h" #include "akregator_part.h" #include "Libkdepim/BroadcastStatus" #include "createfeedcommand.h" #include "createfoldercommand.h" #include "deletesubscriptioncommand.h" #include "editsubscriptioncommand.h" #include "expireitemscommand.h" #include "importfeedlistcommand.h" #include "feed.h" #include "feedlist.h" #include "feedpropertiesdialog.h" #include "fetchqueue.h" #include "folder.h" #include "framemanager.h" #include "kernel.h" #include "notificationmanager.h" #include "openurlrequest.h" #include "progressmanager.h" #include "widgets/searchbar.h" #include "selectioncontroller.h" #include "subscriptionlistjobs.h" #include "subscriptionlistmodel.h" #include "subscriptionlistview.h" #include "tabwidget.h" #include "treenode.h" #include "treenodevisitor.h" #include "types.h" #include "mainframe.h" #include #include "job/downloadarticlejob.h" -#include +#include #include -#include -#include +#include +#include -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include "articleviewer-ng/webengine/articleviewerwebenginewidgetng.h" using namespace Akregator; MainWidget::~MainWidget() { // if m_shuttingDown is false, slotOnShutdown was not called. That // means that not the whole app is shutdown, only the part. So it // should be no risk to do the cleanups now if (!m_shuttingDown) { slotOnShutdown(); } } MainWidget::MainWidget(Part *part, QWidget *parent, ActionManagerImpl *actionManager, const QString &name) : QWidget(parent) , m_feedList() , m_viewMode(NormalView) , m_actionManager(actionManager) , m_feedListManagementInterface(new FeedListManagementImpl) { setObjectName(name); FeedListManagementInterface::setInstance(m_feedListManagementInterface); m_actionManager->initMainWidget(this); m_part = part; m_shuttingDown = false; m_displayingAboutPage = false; setFocusPolicy(Qt::StrongFocus); connect(m_part, &Part::signalSettingsChanged, m_actionManager, &ActionManagerImpl::slotSettingsChanged); QVBoxLayout *lt = new QVBoxLayout(this); lt->setContentsMargins(0, 0, 0, 0); m_horizontalSplitter = new QSplitter(Qt::Horizontal, this); m_horizontalSplitter->setOpaqueResize(true); m_horizontalSplitter->setChildrenCollapsible(false); lt->addWidget(m_horizontalSplitter); connect(Kernel::self()->fetchQueue(), &FetchQueue::signalStarted, this, &MainWidget::slotFetchingStarted); connect(Kernel::self()->fetchQueue(), &FetchQueue::signalStopped, this, &MainWidget::slotFetchingStopped); m_feedListView = new SubscriptionListView(m_horizontalSplitter); m_feedListView->setObjectName(QStringLiteral("feedtree")); m_actionManager->initSubscriptionListView(m_feedListView); connect(m_feedListView, &SubscriptionListView::userActionTakingPlace, this, &MainWidget::ensureArticleTabVisible); m_tabWidget = new TabWidget(m_horizontalSplitter); m_actionManager->initTabWidget(m_tabWidget); connect(m_part, &Part::signalSettingsChanged, this, &MainWidget::slotSettingsChanged); connect(m_tabWidget, &TabWidget::signalCurrentFrameChanged, this, &MainWidget::slotCurrentFrameChanged); connect(m_tabWidget, &TabWidget::signalRemoveFrameRequest, Kernel::self()->frameManager(), &FrameManager::slotRemoveFrame); connect(m_tabWidget, SIGNAL(signalOpenUrlRequest(Akregator::OpenUrlRequest&)), Kernel::self()->frameManager(), SLOT(slotOpenUrlRequest(Akregator::OpenUrlRequest&))); connect(Kernel::self()->frameManager(), &FrameManager::signalFrameAdded, m_tabWidget, &TabWidget::slotAddFrame); connect(Kernel::self()->frameManager(), &FrameManager::signalSelectFrame, m_tabWidget, &TabWidget::slotSelectFrame); connect(Kernel::self()->frameManager(), &FrameManager::signalFrameRemoved, m_tabWidget, &TabWidget::slotRemoveFrame); connect(Kernel::self()->frameManager(), &FrameManager::signalRequestNewFrame, this, &MainWidget::slotRequestNewFrame); connect(Kernel::self()->frameManager(), &FrameManager::signalFrameRemoved, this, &MainWidget::slotFramesChanged); connect(Kernel::self()->frameManager(), &FrameManager::signalCompleted, this, &MainWidget::slotFramesChanged); connect(PimCommon::NetworkManager::self()->networkConfigureManager(), &QNetworkConfigurationManager::onlineStateChanged, this, &MainWidget::slotNetworkStatusChanged); m_tabWidget->setWhatsThis(i18n("You can view multiple articles in several open tabs.")); m_mainTab = new QWidget(this); m_mainTab->setObjectName(QStringLiteral("Article Tab")); m_mainTab->setWhatsThis(i18n("Articles list.")); QVBoxLayout *mainTabLayout = new QVBoxLayout(m_mainTab); mainTabLayout->setContentsMargins(0, 0, 0, 0); m_searchBar = new SearchBar(m_mainTab); if (!Settings::showQuickFilter()) { m_searchBar->hide(); } connect(m_searchBar, &SearchBar::forceLostFocus, this, &MainWidget::slotSetFocusToViewer); m_articleSplitter = new QSplitter(Qt::Vertical, m_mainTab); m_articleSplitter->setObjectName(QStringLiteral("panner2")); m_articleSplitter->setChildrenCollapsible(false); m_articleWidget = new QWidget(m_articleSplitter); QVBoxLayout *articleWidgetLayout = new QVBoxLayout; m_articleWidget->setLayout(articleWidgetLayout); articleWidgetLayout->setContentsMargins(0, 0, 0, 0); articleWidgetLayout->setSpacing(0); m_articleListView = new ArticleListView; articleWidgetLayout->addWidget(m_searchBar); articleWidgetLayout->addWidget(m_articleListView); connect(m_articleListView, &ArticleListView::userActionTakingPlace, this, &MainWidget::ensureArticleTabVisible); m_selectionController = new SelectionController(this); m_selectionController->setArticleLister(m_articleListView); m_selectionController->setFeedSelector(m_feedListView); connect(m_searchBar, &SearchBar::signalSearch, m_selectionController, &AbstractSelectionController::setFilters); connect(m_part, &Part::signalSettingsChanged, m_selectionController, &AbstractSelectionController::settingsChanged); FolderExpansionHandler *expansionHandler = new FolderExpansionHandler(this); connect(m_feedListView, &QTreeView::expanded, expansionHandler, &FolderExpansionHandler::itemExpanded); connect(m_feedListView, &QTreeView::collapsed, expansionHandler, &FolderExpansionHandler::itemCollapsed); m_selectionController->setFolderExpansionHandler(expansionHandler); connect(m_selectionController, &AbstractSelectionController::currentSubscriptionChanged, this, &MainWidget::slotNodeSelected); connect(m_selectionController, &AbstractSelectionController::currentArticleChanged, this, &MainWidget::slotArticleSelected); connect(m_selectionController, &AbstractSelectionController::articleDoubleClicked, this, &MainWidget::slotOpenArticleInBrowser); m_actionManager->initArticleListView(m_articleListView); connect(m_articleListView, &ArticleListView::signalMouseButtonPressed, this, &MainWidget::slotMouseButtonPressed); m_articleViewer = new ArticleViewerWidget(Settings::grantleeDirectory(), m_actionManager->actionCollection(), m_articleSplitter); m_articleListView->setFocusProxy(m_articleViewer); setFocusProxy(m_articleViewer); connect(m_articleViewer, &ArticleViewerWidget::showStatusBarMessage, this, &MainWidget::slotShowStatusBarMessage); connect(m_articleViewer, SIGNAL(signalOpenUrlRequest(Akregator::OpenUrlRequest&)), Kernel::self()->frameManager(), SLOT(slotOpenUrlRequest(Akregator::OpenUrlRequest&))); connect(m_searchBar, &SearchBar::signalSearch, m_articleViewer, &ArticleViewerWidget::setFilters); mainTabLayout->addWidget(m_articleSplitter); m_mainFrame = new MainFrame(this, m_mainTab); m_mainFrame->slotSetTitle(i18n("Articles")); m_mainFrame->setArticleViewer(m_articleViewer); connect(m_articleViewer->articleViewerWidgetNg()->articleViewerNg(), &ArticleViewerWebEngine::articleAction, this, &MainWidget::slotArticleAction); connect(m_tabWidget, &TabWidget::signalCopyInFrame, m_mainFrame, &MainFrame::slotCopyInFrame); connect(m_tabWidget, &TabWidget::signalPrintInFrame, m_mainFrame, &MainFrame::slotPrintInFrame); connect(m_tabWidget, &TabWidget::signalZoomChangedInFrame, m_mainFrame, &MainFrame::slotZoomChangeInFrame); connect(m_tabWidget, &TabWidget::signalPrintPreviewInFrame, m_mainFrame, &MainFrame::slotPrintPreviewInFrame); connect(m_tabWidget, &TabWidget::signalFindTextInFrame, m_mainFrame, &MainFrame::slotFindTextInFrame); connect(m_tabWidget, &TabWidget::signalTextToSpeechInFrame, m_mainFrame, &MainFrame::slotTextToSpeechInFrame); connect(m_tabWidget, &TabWidget::signalSaveLinkAsInFrame, m_mainFrame, &MainFrame::slotSaveLinkAsInFrame); connect(m_tabWidget, &TabWidget::signalCopyLinkAsInFrame, m_mainFrame, &MainFrame::slotCopyLinkAsInFrame); connect(m_tabWidget, &TabWidget::signalCopyImageLocation, m_mainFrame, &MainFrame::slotCopyImageLocationInFrame); connect(m_tabWidget, &TabWidget::signalSaveImageOnDisk, m_mainFrame, &MainFrame::slotSaveImageOnDiskInFrame); connect(m_tabWidget, &TabWidget::signalMute, m_mainFrame, &MainFrame::slotMute); Kernel::self()->frameManager()->slotAddFrame(m_mainFrame); const QList sp1sizes = Settings::splitter1Sizes(); if (sp1sizes.count() >= m_horizontalSplitter->count()) { m_horizontalSplitter->setSizes(sp1sizes); } const QList sp2sizes = Settings::splitter2Sizes(); if (sp2sizes.count() >= m_articleSplitter->count()) { m_articleSplitter->setSizes(sp2sizes); } if (!Settings::self()->disableIntroduction()) { m_articleWidget->hide(); m_articleViewer->displayAboutPage(); m_mainFrame->slotSetTitle(i18n("About")); m_displayingAboutPage = true; } m_fetchTimer = new QTimer(this); connect(m_fetchTimer, &QTimer::timeout, this, &MainWidget::slotDoIntervalFetches); m_fetchTimer->start(1000 * 60); // delete expired articles once per hour m_expiryTimer = new QTimer(this); connect(m_expiryTimer, &QTimer::timeout, this, &MainWidget::slotDeleteExpiredArticles); m_expiryTimer->start(3600 * 1000); m_markReadTimer = new QTimer(this); m_markReadTimer->setSingleShot(true); connect(m_markReadTimer, &QTimer::timeout, this, &MainWidget::slotSetCurrentArticleReadDelayed); setFeedList(QSharedPointer(new FeedList(Kernel::self()->storage()))); switch (Settings::viewMode()) { case CombinedView: slotCombinedView(); break; case WidescreenView: slotWidescreenView(); break; default: slotNormalView(); } if (!Settings::resetQuickFilterOnNodeChange()) { m_searchBar->slotSetStatus(Settings::statusFilter()); m_searchBar->slotSetText(Settings::textFilter()); } } void MainWidget::slotSettingsChanged() { m_tabWidget->slotSettingsChanged(); m_articleViewer->updateAfterConfigChanged(); } void MainWidget::slotSetFocusToViewer() { m_articleViewer->setFocus(); } void MainWidget::slotOnShutdown() { disconnect(m_tabWidget, &TabWidget::signalCurrentFrameChanged, this, &MainWidget::slotCurrentFrameChanged); m_shuttingDown = true; // close all pageviewers in a controlled way // fixes bug 91660, at least when no part loading data while (m_tabWidget->count() > 1) { // remove frames until only the main frame remains m_tabWidget->setCurrentIndex(m_tabWidget->count() - 1); // select last page m_tabWidget->slotRemoveCurrentFrame(); } Kernel::self()->fetchQueue()->slotAbort(); setFeedList(QSharedPointer()); delete m_feedListManagementInterface; delete m_feedListView; // call delete here, so that the header settings will get saved delete m_articleListView; // same for this one delete m_mainTab; delete m_mainFrame; m_mainFrame = nullptr; Settings::self()->save(); } void MainWidget::saveSettings() { const QList spl1 = m_horizontalSplitter->sizes(); if (std::count(spl1.begin(), spl1.end(), 0) == 0) { Settings::setSplitter1Sizes(spl1); } const QList spl2 = m_articleSplitter->sizes(); if (std::count(spl2.begin(), spl2.end(), 0) == 0) { Settings::setSplitter2Sizes(spl2); } Settings::setViewMode(m_viewMode); Settings::self()->save(); } void MainWidget::connectFrame(Akregator::WebEngineFrame *frame) { connect(m_tabWidget, &TabWidget::signalCopyInFrame, frame, &WebEngineFrame::slotCopyInFrame); connect(m_tabWidget, &TabWidget::signalPrintInFrame, frame, &WebEngineFrame::slotPrintInFrame); connect(m_tabWidget, &TabWidget::signalZoomChangedInFrame, frame, &WebEngineFrame::slotZoomChangeInFrame); connect(m_tabWidget, &TabWidget::signalPrintPreviewInFrame, frame, &WebEngineFrame::slotPrintPreviewInFrame); connect(m_tabWidget, &TabWidget::signalFindTextInFrame, frame, &WebEngineFrame::slotFindTextInFrame); connect(m_tabWidget, &TabWidget::signalTextToSpeechInFrame, frame, &WebEngineFrame::slotTextToSpeechInFrame); connect(m_tabWidget, &TabWidget::signalSaveLinkAsInFrame, frame, &WebEngineFrame::slotSaveLinkAsInFrame); connect(m_tabWidget, &TabWidget::signalCopyLinkAsInFrame, frame, &WebEngineFrame::slotCopyLinkAsInFrame); connect(m_tabWidget, &TabWidget::signalCopyImageLocation, frame, &WebEngineFrame::slotCopyImageLocationInFrame); connect(m_tabWidget, &TabWidget::signalSaveImageOnDisk, frame, &WebEngineFrame::slotSaveImageOnDiskInFrame); connect(m_tabWidget, &TabWidget::signalMute, frame, &WebEngineFrame::slotMute); connect(frame, &WebEngineFrame::showStatusBarMessage, this, &MainWidget::slotShowStatusBarMessage); connect(frame, &WebEngineFrame::signalIconChanged, m_tabWidget, &TabWidget::slotSetIcon); connect(frame, &WebEngineFrame::webPageMutedOrAudibleChanged, m_tabWidget, &TabWidget::slotWebPageMutedOrAudibleChanged); } void MainWidget::slotRequestNewFrame(int &frameId) { WebEngineFrame *frame = new WebEngineFrame(m_actionManager->actionCollection(), m_tabWidget); connectFrame(frame); Kernel::self()->frameManager()->slotAddFrame(frame); frameId = frame->id(); } void MainWidget::sendArticle(bool attach) { QByteArray text; QString title; Frame *frame = Kernel::self()->frameManager()->currentFrame(); if (frame && frame->id() > 0) { // are we in some other tab than the articlelist? text = frame->url().toString().toLatin1(); title = frame->title(); } else { // nah, we're in articlelist.. const Article article = m_selectionController->currentArticle(); if (!article.isNull()) { text = article.link().toDisplayString().toLatin1(); title = Akregator::Utils::convertHtmlTags(article.title()); } } if (text.isEmpty()) { return; } sendArticle(text, title, attach); } void MainWidget::cleanUpDownloadFile() { for (QPointer job : qAsConst(mListDownloadArticleJobs)) { if (job) { job->forceCleanupTemporaryFile(); } } } void MainWidget::sendArticle(const QByteArray &text, const QString &title, bool attach) { if (attach) { QPointer download = new Akregator::DownloadArticleJob(this); download->setArticleUrl(QUrl(QString::fromUtf8(text))); download->setText(QString::fromUtf8(text)); download->setTitle(title); mListDownloadArticleJobs.append(download); download->start(); } else { QUrlQuery query; query.addQueryItem(QStringLiteral("subject"), title); query.addQueryItem(QStringLiteral("body"), QString::fromUtf8(text)); QUrl url; url.setScheme(QStringLiteral("mailto")); url.setQuery(query); QDesktopServices::openUrl(url); } } void MainWidget::importFeedList(const QDomDocument &doc) { ImportFeedListCommand *cmd = new ImportFeedListCommand; cmd->setParentWidget(this); cmd->setFeedListDocument(doc); cmd->setTargetList(m_feedList); cmd->start(); } void MainWidget::setFeedList(const QSharedPointer &list) { if (list == m_feedList) { return; } const QSharedPointer oldList = m_feedList; m_feedList = list; if (m_feedList) { connect(m_feedList.data(), &FeedList::unreadCountChanged, this, &MainWidget::slotSetTotalUnread); } slotSetTotalUnread(); m_feedListManagementInterface->setFeedList(m_feedList); Kernel::self()->setFeedList(m_feedList); ProgressManager::self()->setFeedList(m_feedList); m_selectionController->setFeedList(m_feedList); if (oldList) { oldList->disconnect(this); } slotDeleteExpiredArticles(); } void MainWidget::deleteExpiredArticles(const QSharedPointer &list) { if (!list) { return; } ExpireItemsCommand *cmd = new ExpireItemsCommand(this); cmd->setParentWidget(this); cmd->setFeedList(list); cmd->setFeeds(list->feedIds()); cmd->start(); } void MainWidget::slotDeleteExpiredArticles() { deleteExpiredArticles(m_feedList); } QDomDocument MainWidget::feedListToOPML() { QDomDocument dom; if (m_feedList) { dom = m_feedList->toOpml(); } return dom; } void MainWidget::addFeedToGroup(const QString &url, const QString &groupName) { // Locate the group. const QList namedGroups = m_feedList->findByTitle(groupName); Folder *group = nullptr; for (TreeNode *const candidate : namedGroups) { if (candidate->isGroup()) { group = static_cast(candidate); break; } } if (!group) { Folder *g = new Folder(groupName); m_feedList->allFeedsFolder()->appendChild(g); group = g; } // Invoke the Add Feed dialog with url filled in. addFeed(url, nullptr, group, true); } void MainWidget::slotNormalView() { if (m_viewMode == NormalView) { return; } if (m_viewMode == CombinedView) { m_articleWidget->show(); const Article article = m_selectionController->currentArticle(); if (!article.isNull()) { m_articleViewer->showArticle(article); } else { m_articleViewer->slotShowSummary(m_selectionController->selectedSubscription()); } } m_articleSplitter->setOrientation(Qt::Vertical); m_viewMode = NormalView; Settings::setViewMode(m_viewMode); } void MainWidget::slotWidescreenView() { if (m_viewMode == WidescreenView) { return; } if (m_viewMode == CombinedView) { m_articleWidget->show(); Article article = m_selectionController->currentArticle(); if (!article.isNull()) { m_articleViewer->showArticle(article); } else { m_articleViewer->slotShowSummary(m_selectionController->selectedSubscription()); } } m_articleSplitter->setOrientation(Qt::Horizontal); m_viewMode = WidescreenView; Settings::setViewMode(m_viewMode); } void MainWidget::slotCombinedView() { if (m_viewMode == CombinedView) { return; } m_articleListView->slotClear(); m_articleWidget->hide(); m_viewMode = CombinedView; Settings::setViewMode(m_viewMode); } void MainWidget::slotMoveCurrentNodeUp() { TreeNode *current = m_selectionController->selectedSubscription(); if (!current) { return; } TreeNode *prev = current->prevSibling(); Folder *parent = current->parent(); if (!prev || !parent) { return; } parent->removeChild(prev); parent->insertChild(prev, current); m_feedListView->ensureNodeVisible(current); } void MainWidget::slotMoveCurrentNodeDown() { TreeNode *current = m_selectionController->selectedSubscription(); if (!current) { return; } TreeNode *next = current->nextSibling(); Folder *parent = current->parent(); if (!next || !parent) { return; } parent->removeChild(current); parent->insertChild(current, next); m_feedListView->ensureNodeVisible(current); } void MainWidget::slotMoveCurrentNodeLeft() { TreeNode *current = m_selectionController->selectedSubscription(); if (!current || !current->parent() || !current->parent()->parent()) { return; } Folder *parent = current->parent(); Folder *grandparent = current->parent()->parent(); parent->removeChild(current); grandparent->insertChild(current, parent); m_feedListView->ensureNodeVisible(current); } void MainWidget::slotMoveCurrentNodeRight() { TreeNode *current = m_selectionController->selectedSubscription(); if (!current || !current->parent()) { return; } TreeNode *prev = current->prevSibling(); if (prev && prev->isGroup()) { Folder *fg = static_cast(prev); current->parent()->removeChild(current); fg->appendChild(current); m_feedListView->ensureNodeVisible(current); } } void MainWidget::slotSendLink() { sendArticle(); } void MainWidget::slotSendFile() { sendArticle(true); } void MainWidget::slotNodeSelected(TreeNode *node) { m_markReadTimer->stop(); if (m_displayingAboutPage) { m_mainFrame->slotSetTitle(i18n("Articles")); if (m_viewMode != CombinedView) { m_articleWidget->show(); } if (Settings::showQuickFilter()) { m_searchBar->show(); } m_displayingAboutPage = false; } m_tabWidget->setCurrentWidget(m_mainFrame); if (Settings::resetQuickFilterOnNodeChange()) { m_searchBar->slotClearSearch(); } if (m_viewMode == CombinedView) { m_articleViewer->showNode(node); } else { m_articleViewer->slotShowSummary(node); } if (node) { m_mainFrame->setWindowTitle(node->title()); } m_actionManager->slotNodeSelected(node); } void MainWidget::slotFeedAdd() { Folder *group = nullptr; if (!m_selectionController->selectedSubscription()) { group = m_feedList->allFeedsFolder(); } else { if (m_selectionController->selectedSubscription()->isGroup()) { group = static_cast(m_selectionController->selectedSubscription()); } else { group = m_selectionController->selectedSubscription()->parent(); } } TreeNode *const lastChild = !group->children().isEmpty() ? group->children().last() : nullptr; addFeed(QString(), lastChild, group, false); } void MainWidget::addFeed(const QString &url, TreeNode *after, Folder *parent, bool autoExec) { CreateFeedCommand *cmd(new CreateFeedCommand(this)); cmd->setParentWidget(this); cmd->setPosition(parent, after); cmd->setRootFolder(m_feedList->allFeedsFolder()); cmd->setAutoExecute(autoExec); cmd->setUrl(url); cmd->setSubscriptionListView(m_feedListView); cmd->start(); } void MainWidget::slotFeedAddGroup() { CreateFolderCommand *cmd = new CreateFolderCommand(this); cmd->setParentWidget(this); cmd->setSelectedSubscription(m_selectionController->selectedSubscription()); cmd->setRootFolder(m_feedList->allFeedsFolder()); cmd->setSubscriptionListView(m_feedListView); cmd->start(); } void MainWidget::slotFeedRemove() { TreeNode *selectedNode = m_selectionController->selectedSubscription(); // don't delete root element! (safety valve) if (!selectedNode || selectedNode == m_feedList->allFeedsFolder()) { return; } DeleteSubscriptionCommand *cmd = new DeleteSubscriptionCommand(this); cmd->setParentWidget(this); cmd->setSubscription(m_feedList, selectedNode->id()); cmd->start(); } void MainWidget::slotFeedModify() { TreeNode *const node = m_selectionController->selectedSubscription(); if (!node) { return; } EditSubscriptionCommand *cmd = new EditSubscriptionCommand(this); cmd->setParentWidget(this); cmd->setSubscription(m_feedList, node->id()); cmd->setSubscriptionListView(m_feedListView); cmd->start(); } void MainWidget::slotNextUnreadArticle() { ensureArticleTabVisible(); if (m_viewMode == CombinedView) { m_feedListView->slotNextUnreadFeed(); return; } TreeNode *sel = m_selectionController->selectedSubscription(); if (sel && sel->unread() > 0) { m_articleListView->slotNextUnreadArticle(); } else { m_feedListView->slotNextUnreadFeed(); } } void MainWidget::slotPrevUnreadArticle() { ensureArticleTabVisible(); if (m_viewMode == CombinedView) { m_feedListView->slotPrevUnreadFeed(); return; } TreeNode *sel = m_selectionController->selectedSubscription(); if (sel && sel->unread() > 0) { m_articleListView->slotPreviousUnreadArticle(); } else { m_feedListView->slotPrevUnreadFeed(); } } void MainWidget::slotMarkAllFeedsRead() { KJob *job = m_feedList->createMarkAsReadJob(); connect(job, &KJob::finished, m_selectionController, &AbstractSelectionController::forceFilterUpdate); job->start(); } void MainWidget::slotMarkAllRead() { if (!m_selectionController->selectedSubscription()) { return; } KJob *job = m_selectionController->selectedSubscription()->createMarkAsReadJob(); connect(job, &KJob::finished, m_selectionController, &AbstractSelectionController::forceFilterUpdate); job->start(); } void MainWidget::slotSetTotalUnread() { Q_EMIT signalUnreadCountChanged(m_feedList ? m_feedList->unread() : 0); } void MainWidget::slotDoIntervalFetches() { if (!m_feedList) { return; } #if 0 // the following solid check apparently doesn't work reliably and causes // interval fetching not working although the user is actually online (but solid reports he's not const Networking::Status status = Solid::Networking::status(); if (status != Networking::Connected && status != Networking::Unknown) { return; } #endif m_feedList->addToFetchQueue(Kernel::self()->fetchQueue(), true); } void MainWidget::slotFetchCurrentFeed() { if (!m_selectionController->selectedSubscription()) { return; } if (isNetworkAvailable()) { m_selectionController->selectedSubscription()->slotAddToFetchQueue(Kernel::self()->fetchQueue()); } else { m_mainFrame->slotSetStatusText(i18n("Networking is not available.")); } } void MainWidget::slotFetchAllFeeds() { if (m_feedList && isNetworkAvailable()) { m_feedList->addToFetchQueue(Kernel::self()->fetchQueue()); } else if (m_feedList) { m_mainFrame->slotSetStatusText(i18n("Networking is not available.")); } } void MainWidget::slotFetchingStarted() { m_mainFrame->slotSetState(Frame::Started); m_actionManager->action(QStringLiteral("feed_stop"))->setEnabled(true); m_mainFrame->slotSetStatusText(i18n("Fetching Feeds...")); } void MainWidget::slotFetchingStopped() { m_mainFrame->slotSetState(Frame::Completed); m_actionManager->action(QStringLiteral("feed_stop"))->setEnabled(false); m_mainFrame->slotSetStatusText(QString()); } void MainWidget::slotArticleSelected(const Akregator::Article &article) { if (m_viewMode == CombinedView) { return; } m_markReadTimer->stop(); Q_ASSERT(article.isNull() || article.feed()); QVector
articles = m_selectionController->selectedArticles(); Q_EMIT signalArticlesSelected(articles); KToggleAction *const maai = qobject_cast(m_actionManager->action(QStringLiteral("article_set_status_important"))); Q_ASSERT(maai); maai->setChecked(article.keep()); m_articleViewer->showArticle(article); if (m_selectionController->selectedArticles().isEmpty()) { m_articleListView->setCurrentIndex(m_selectionController->currentArticleIndex()); } if (article.isNull() || article.status() == Akregator::Read) { return; } if (!Settings::useMarkReadDelay()) { return; } const int delay = Settings::markReadDelay(); if (delay > 0) { m_markReadTimer->start(delay * 1000); } else { Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob; const Akregator::ArticleId aid = { article.feed()->xmlUrl(), article.guid() }; job->setStatus(aid, Akregator::Read); job->start(); } } void MainWidget::slotMouseButtonPressed(int button, const QUrl &url) { if (button != Qt::MidButton) { return; } if (!url.isValid()) { return; } OpenUrlRequest req(url); switch (Settings::mMBBehaviour()) { case Settings::EnumMMBBehaviour::OpenInExternalBrowser: req.setOptions(OpenUrlRequest::ExternalBrowser); break; case Settings::EnumMMBBehaviour::OpenInBackground: req.setOptions(OpenUrlRequest::NewTab); req.setOpenInBackground(true); break; default: req.setOptions(OpenUrlRequest::NewTab); req.setOpenInBackground(false); } Kernel::self()->frameManager()->slotOpenUrlRequest(req); } void MainWidget::slotOpenHomepage() { Feed *feed = dynamic_cast(m_selectionController->selectedSubscription()); if (!feed) { return; } QUrl url(feed->htmlUrl()); if (url.isValid()) { OpenUrlRequest req(url); req.setOptions(OpenUrlRequest::ExternalBrowser); Kernel::self()->frameManager()->slotOpenUrlRequest(req); } } void MainWidget::slotOpenSelectedArticlesInBrowser() { const QVector
articles = m_selectionController->selectedArticles(); for (const Akregator::Article &article : articles) { slotOpenArticleInBrowser(article); } } void MainWidget::slotOpenArticleInBrowser(const Akregator::Article &article) { if (!article.isNull() && article.link().isValid()) { OpenUrlRequest req(article.link()); req.setOptions(OpenUrlRequest::ExternalBrowser); Kernel::self()->frameManager()->slotOpenUrlRequest(req); } } void MainWidget::openSelectedArticles(bool openInBackground) { const QVector
articles = m_selectionController->selectedArticles(); for (const Akregator::Article &article : articles) { const QUrl url = article.link(); if (!url.isValid()) { continue; } OpenUrlRequest req(url); req.setOptions(OpenUrlRequest::NewTab); if (openInBackground) { req.setOpenInBackground(true); Kernel::self()->frameManager()->slotOpenUrlRequest(req, false /*don't use settings for open in background*/); } else { Kernel::self()->frameManager()->slotOpenUrlRequest(req); } } } void MainWidget::currentArticleInfo(QString &link, QString &title) { const Article article = m_selectionController->currentArticle(); if (article.isNull()) { return; } if (article.link().isValid()) { link = article.link().url(); title = Utils::convertHtmlTags(article.title()); } } void MainWidget::updateQuickSearchLineText() { m_searchBar->updateQuickSearchLineText(m_actionManager->quickSearchLineText()); } void MainWidget::slotCopyLinkAddress() { const Article article = m_selectionController->currentArticle(); if (article.isNull()) { return; } QString link; if (article.link().isValid()) { link = article.link().url(); QClipboard *cb = QApplication::clipboard(); cb->setText(link, QClipboard::Clipboard); // don't set url to selection as it's a no-no according to a fd.o spec //cb->setText(link, QClipboard::Selection); } } void MainWidget::slotToggleShowQuickFilter() { if (Settings::showQuickFilter()) { Settings::setShowQuickFilter(false); m_searchBar->slotClearSearch(); m_searchBar->hide(); } else { Settings::setShowQuickFilter(true); if (!m_displayingAboutPage) { m_searchBar->show(); } } } void MainWidget::slotArticleDelete() { if (m_viewMode == CombinedView) { return; } const QVector
articles = m_selectionController->selectedArticles(); QString msg; switch (articles.count()) { case 0: return; case 1: msg = i18n("Are you sure you want to delete article %1?", articles.first().title()); break; default: msg = i18np("Are you sure you want to delete the selected article?", "Are you sure you want to delete the %1 selected articles?", articles.count()); } if (KMessageBox::warningContinueCancel(this, msg, i18n("Delete Article"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QStringLiteral("Disable delete article confirmation")) != KMessageBox::Continue) { return; } TreeNode *const selected = m_selectionController->selectedSubscription(); if (selected) { selected->setNotificationMode(false); } Akregator::ArticleDeleteJob *job = new Akregator::ArticleDeleteJob; for (const Akregator::Article &i : articles) { Feed *const feed = i.feed(); if (!feed) { continue; } const Akregator::ArticleId aid = { feed->xmlUrl(), i.guid() }; job->appendArticleId(aid); } job->start(); if (selected) { selected->setNotificationMode(true); } } void MainWidget::slotFramesChanged() { // We need to wait till the frame is fully loaded QMetaObject::invokeMethod(m_part, &Akregator::Part::slotAutoSave, Qt::QueuedConnection); } void MainWidget::slotArticleToggleKeepFlag(bool) { const QVector
articles = m_selectionController->selectedArticles(); if (articles.isEmpty()) { return; } bool allFlagsSet = true; for (const Akregator::Article &i : articles) { allFlagsSet = allFlagsSet && i.keep(); if (!allFlagsSet) { break; } } Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob; for (const Akregator::Article &i : articles) { const Akregator::ArticleId aid = { i.feed()->xmlUrl(), i.guid() }; job->setKeep(aid, !allFlagsSet); } job->start(); } namespace { void setArticleStatus(const QString &feedUrl, const QString &articleId, int status) { if (!feedUrl.isEmpty() && !articleId.isEmpty()) { Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob; const Akregator::ArticleId aid = { feedUrl, articleId }; job->setStatus(aid, status); job->start(); } } void setSelectedArticleStatus(const Akregator::AbstractSelectionController *controller, int status) { const QVector articles = controller->selectedArticles(); if (articles.isEmpty()) { return; } Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob; for (const Akregator::Article &i : articles) { const Akregator::ArticleId aid = { i.feed()->xmlUrl(), i.guid() }; job->setStatus(aid, status); } job->start(); } } void MainWidget::slotSetSelectedArticleRead() { ::setSelectedArticleStatus(m_selectionController, Akregator::Read); } void MainWidget::slotSetSelectedArticleUnread() { ::setSelectedArticleStatus(m_selectionController, Akregator::Unread); } void MainWidget::slotSetSelectedArticleNew() { ::setSelectedArticleStatus(m_selectionController, Akregator::New); } void MainWidget::slotSetCurrentArticleReadDelayed() { const Article article = m_selectionController->currentArticle(); if (article.isNull()) { return; } Akregator::ArticleModifyJob *const job = new Akregator::ArticleModifyJob; const Akregator::ArticleId aid = { article.feed()->xmlUrl(), article.guid() }; job->setStatus(aid, Akregator::Read); job->start(); } void MainWidget::slotShowStatusBarMessage(const QString &msg) { KPIM::BroadcastStatus::instance()->setStatusMsg(msg); } void MainWidget::readProperties(const KConfigGroup &config) { if (!Settings::resetQuickFilterOnNodeChange()) { // read filter settings m_searchBar->slotSetText(config.readEntry("searchLine")); m_searchBar->slotSetStatus(config.readEntry("searchCombo").toInt()); } const QString currentTabName = config.readEntry("CurrentTab"); // Reopen tabs const QStringList childList = config.readEntry(QStringLiteral("Children"), QStringList()); int currentFrameId = -1; for (const QString &framePrefix : childList) { WebEngineFrame *const frame = new WebEngineFrame(m_actionManager->actionCollection(), m_tabWidget); frame->loadConfig(config, framePrefix + QLatin1Char('_')); connectFrame(frame); Kernel::self()->frameManager()->slotAddFrame(frame); if (currentTabName == framePrefix) { currentFrameId = frame->id(); } } if (currentFrameId != -1) { m_tabWidget->slotSelectFrame(currentFrameId); } } void MainWidget::saveProperties(KConfigGroup &config) { // save filter settings const QString searchStr(m_searchBar->text()); if (searchStr.isEmpty()) { config.deleteEntry("searchLine"); } else { config.writeEntry("searchLine", m_searchBar->text()); } config.writeEntry("searchCombo", static_cast(m_searchBar->status())); Kernel::self()->frameManager()->saveProperties(config); } void MainWidget::ensureArticleTabVisible() { m_tabWidget->setCurrentWidget(m_mainFrame); } void MainWidget::slotReloadAllTabs() { m_tabWidget->slotReloadAllTabs(); } bool MainWidget::isNetworkAvailable() const { return PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline(); } void MainWidget::slotNetworkStatusChanged(bool status) { if (status) { m_mainFrame->slotSetStatusText(i18n("Networking is available now.")); this->slotFetchAllFeeds(); } else { m_mainFrame->slotSetStatusText(i18n("Networking is not available.")); } } void MainWidget::slotOpenSelectedArticles() { openSelectedArticles(false); } void MainWidget::slotOpenSelectedArticlesInBackground() { openSelectedArticles(true); } void MainWidget::slotCurrentFrameChanged(int frameId) { Kernel::self()->frameManager()->slotChangeFrame(frameId); m_actionManager->zoomActionMenu()->setZoomFactor(Kernel::self()->frameManager()->currentFrame()->zoomFactor() * 100); } void MainWidget::slotFocusQuickSearch() { m_searchBar->setFocusSearchLine(); } void MainWidget::slotArticleAction(Akregator::ArticleViewerWebEngine::ArticleAction type, const QString &articleId, const QString &feed) { switch (type) { case ArticleViewerWebEngine::DeleteAction: { Akregator::ArticleDeleteJob *job = new Akregator::ArticleDeleteJob; const Akregator::ArticleId aid = { feed, articleId }; job->appendArticleId(aid); job->start(); break; } case ArticleViewerWebEngine::MarkAsRead: ::setArticleStatus(feed, articleId, Akregator::Read); break; case ArticleViewerWebEngine::MarkAsUnRead: ::setArticleStatus(feed, articleId, Akregator::Unread); break; case ArticleViewerWebEngine::MarkAsImportant: { Akregator::ArticleModifyJob *job = new Akregator::ArticleModifyJob; const Akregator::Article article = m_feedList->findArticle(feed, articleId); const Akregator::ArticleId aid = { feed, articleId }; job->setKeep(aid, !article.keep()); job->start(); break; } case ArticleViewerWebEngine::SendUrlArticle: { case ArticleViewerWebEngine::SendFileArticle: const Article article = m_feedList->findArticle(feed, articleId); const QByteArray text = article.link().toDisplayString().toLatin1(); const QString title = Akregator::Utils::convertHtmlTags(article.title()); if (text.isEmpty()) { return; } sendArticle(text, title, (type == ArticleViewerWebEngine::SendFileArticle)); break; } case ArticleViewerWebEngine::OpenInBackgroundTab: { const Akregator::Article article = m_feedList->findArticle(feed, articleId); const QUrl url = article.link(); if (url.isValid()) { OpenUrlRequest req(url); req.setOptions(OpenUrlRequest::NewTab); req.setOpenInBackground(true); Kernel::self()->frameManager()->slotOpenUrlRequest(req, false /*don't use settings for open in background*/); } break; } case ArticleViewerWebEngine::OpenInExternalBrowser: { const Akregator::Article article = m_feedList->findArticle(feed, articleId); slotOpenArticleInBrowser(article); break; } } } diff --git a/src/notificationmanager.cpp b/src/notificationmanager.cpp index 5e8e1972..c0b821ec 100644 --- a/src/notificationmanager.cpp +++ b/src/notificationmanager.cpp @@ -1,146 +1,146 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "notificationmanager.h" #include "feed.h" #include -#include +#include #include #include using namespace Akregator; NotificationManager::NotificationManager(QObject *parent) : QObject(parent) { m_intervalsLapsed = 0; m_checkInterval = 2000; m_maxIntervals = 10; m_running = false; m_addedInLastInterval = false; m_maxArticles = 20; m_widget = nullptr; } NotificationManager::~NotificationManager() { m_self = nullptr; } void NotificationManager::setWidget(QWidget *widget, const QString &componentName) { m_widget = widget; m_componantName = componentName.isEmpty() ? KAboutData::applicationData().componentName() : componentName; } void NotificationManager::slotNotifyArticle(const Article &article) { m_articles.append(article); m_addedInLastInterval = true; if (!m_running) { m_running = true; QTimer::singleShot(m_checkInterval, this, &NotificationManager::slotIntervalCheck); } } void NotificationManager::slotNotifyFeeds(const QStringList &feeds) { const int feedsCount(feeds.count()); if (feedsCount == 1) { KNotification::event(QStringLiteral("FeedAdded"), i18n("Feed added:\n %1", feeds[0]), QPixmap(), m_widget, KNotification::CloseOnTimeout, m_componantName); } else if (feedsCount > 1) { QString message; QStringList::ConstIterator end = feeds.constEnd(); for (QStringList::ConstIterator it = feeds.constBegin(); it != end; ++it) { message += *it + QLatin1Char('\n'); } KNotification::event(QStringLiteral("FeedAdded"), i18n("Feeds added:\n %1", message), QPixmap(), m_widget, KNotification::CloseOnTimeout, m_componantName); } } void NotificationManager::doNotify() { QString message = QStringLiteral(""); QString feedTitle; int entriesCount = 1; const int maxNewArticlesShown = 2; // adding information about how many new articles auto feedClosure = [&entriesCount, &message]() { if ((entriesCount - maxNewArticlesShown) > 1) { message += i18np("and 1 other", "and %1 others", entriesCount - maxNewArticlesShown - 1) + QLatin1String("
"); } }; for (const Article &i : qAsConst(m_articles)) { const QString currentFeedTitle(i.feed()->title()); if (feedTitle != currentFeedTitle) { // closing previous feed, if any, and resetting the counter feedClosure(); entriesCount = 1; // starting a new feed feedTitle = currentFeedTitle; message += QStringLiteral("

%1:

").arg(feedTitle); } // check not exceeding maxNewArticlesShown per feed if (entriesCount <= maxNewArticlesShown) { message += i.title() + QLatin1String("
"); } entriesCount++; } feedClosure(); message += QLatin1String(""); KNotification::event(QStringLiteral("NewArticles"), message, QPixmap(), m_widget, KNotification::CloseOnTimeout, m_componantName); m_articles.clear(); m_running = false; m_intervalsLapsed = 0; m_addedInLastInterval = false; } void NotificationManager::slotIntervalCheck() { if (!m_running) { return; } m_intervalsLapsed++; if (!m_addedInLastInterval || m_articles.count() >= m_maxArticles || m_intervalsLapsed >= m_maxIntervals) { doNotify(); } else { m_addedInLastInterval = false; QTimer::singleShot(m_checkInterval, this, &NotificationManager::slotIntervalCheck); } } NotificationManager *NotificationManager::m_self = nullptr; NotificationManager *NotificationManager::self() { static NotificationManager self; if (!m_self) { m_self = &self; } return m_self; } diff --git a/src/pluginmanager.cpp b/src/pluginmanager.cpp index d77d701a..a75c6251 100644 --- a/src/pluginmanager.cpp +++ b/src/pluginmanager.cpp @@ -1,205 +1,205 @@ /*************************************************************************** begin : 2004/03/12 copyright : (C) Mark Kretschmann email : markey@web.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "pluginmanager.h" #include "plugin.h" #include #include #include "akregator_debug.h" #include -#include +#include using std::vector; using Akregator::Plugin; namespace Akregator { vector PluginManager::m_store; ///////////////////////////////////////////////////////////////////////////////////// // PUBLIC INTERFACE ///////////////////////////////////////////////////////////////////////////////////// KService::List PluginManager::query(const QString &constraint) { // Add versioning constraint QString str = QStringLiteral("[X-KDE-akregator-framework-version] == "); str += QString::number(AKREGATOR_PLUGIN_INTERFACE_VERSION); str += QStringLiteral(" and "); if (!constraint.trimmed().isEmpty()) { str += constraint + QStringLiteral(" and "); } str += QStringLiteral("[X-KDE-akregator-rank] > 0"); qCDebug(AKREGATOR_LOG) << "Plugin trader constraint:" << str; return KServiceTypeTrader::self()->query(QStringLiteral("Akregator/Plugin"), str); } Plugin * PluginManager::createFromQuery(const QString &constraint) { KService::List offers = query(constraint); if (offers.isEmpty()) { qCWarning(AKREGATOR_LOG) << "No matching plugin found."; return nullptr; } // Select plugin with highest rank int rank = 0; int current = 0; for (int i = 0; i < offers.count(); ++i) { if (offers[i]->property(QStringLiteral("X-KDE-akregator-rank")).toInt() > rank) { current = i; } } return createFromService(offers[current]); } Plugin * PluginManager::createFromService(const KService::Ptr &service, QObject *parent) { qCDebug(AKREGATOR_LOG) << "Trying to load:" << service->library(); KPluginLoader loader(*service); KPluginFactory *factory = loader.factory(); if (!factory) { qCWarning(AKREGATOR_LOG) << QStringLiteral(" Could not create plugin factory for: %1\n" " Error message: %2").arg(service->library(), loader.errorString()); return nullptr; } Plugin *const plugin = factory->create(parent); //put plugin into store StoreItem item; item.plugin = plugin; item.service = service; m_store.push_back(item); dump(service); return plugin; } void PluginManager::unload(Plugin *plugin) { #ifdef TEMPORARILY_REMOVED vector::iterator iter = lookupPlugin(plugin); if (iter != m_store.end()) { delete(*iter).plugin; qCDebug(AKREGATOR_LOG) << "Unloading library:" << (*iter).service->library(); //PENDING(kdab,frank) Review (*iter).library->unload(); m_store.erase(iter); } else { qCWarning(AKREGATOR_LOG) << "Could not unload plugin (not found in store)."; } #else //TEMPORARILY_REMOVED Q_UNUSED(plugin) qCWarning(AKREGATOR_LOG) << "PluginManager::unload temporarily disabled"; #endif //TEMPORARILY_REMOVED } KService::Ptr PluginManager::getService(const Plugin *plugin) { if (!plugin) { qCWarning(AKREGATOR_LOG) << "pointer == NULL"; return KService::Ptr(nullptr); } //search plugin in store vector::const_iterator iter = lookupPlugin(plugin); if (iter == m_store.end()) { qCWarning(AKREGATOR_LOG) << "Plugin not found in store."; return KService::Ptr(nullptr); } return (*iter).service; } void PluginManager::showAbout(const QString &constraint) { KService::List offers = query(constraint); if (offers.isEmpty()) { return; } KService::Ptr s = offers.front(); const QString body = QStringLiteral("%1%2"); QString str = QStringLiteral(""); str += body.arg(i18nc("Name of the plugin", "Name"), s->name()); str += body.arg(i18nc("Library name", "Library"), s->library()); str += body.arg(i18nc("Plugin authors", "Authors"), s->property(QStringLiteral("X-KDE-akregator-authors")).toStringList().join(QLatin1Char('\n'))); str += body.arg(i18nc("Plugin authors' emaila addresses", "Email"), s->property(QStringLiteral("X-KDE-akregator-email")).toStringList().join(QLatin1Char('\n'))); str += body.arg(i18nc("Plugin version", "Version"), s->property(QStringLiteral("X-KDE-akregator-version")).toString()); str += body.arg(i18nc("Framework version plugin requires", "Framework Version"), s->property(QStringLiteral("X-KDE-akregator-framework-version")).toString()); str += QStringLiteral("
"); KMessageBox::information(nullptr, str, i18n("Plugin Information")); } void PluginManager::dump(const KService::Ptr &service) { qCDebug(AKREGATOR_LOG) << "PluginManager Service Info:" << "---------------------------" << "name : " << service->name() << "library : " << service->library() << "desktopEntryPath : " << service->entryPath() << "X-KDE-akregator-plugintype : " << service->property(QStringLiteral("X-KDE-akregator-plugintype")).toString() << "X-KDE-akregator-name : " << service->property(QStringLiteral("X-KDE-akregator-name")).toString() << "X-KDE-akregator-authors : " << service->property(QStringLiteral("X-KDE-akregator-authors")).toStringList() << "X-KDE-akregator-rank : " << service->property(QStringLiteral("X-KDE-akregator-rank")).toString() << "X-KDE-akregator-version : " << service->property(QStringLiteral("X-KDE-akregator-version")).toString() << "X-KDE-akregator-framework-version: " << service->property(QStringLiteral("X-KDE-akregator-framework-version")).toString(); } ///////////////////////////////////////////////////////////////////////////////////// // PRIVATE INTERFACE ///////////////////////////////////////////////////////////////////////////////////// vector::iterator PluginManager::lookupPlugin(const Plugin *plugin) { vector::iterator iter; //search plugin pointer in store vector::const_iterator end; for (iter = m_store.begin(); iter != end; ++iter) { if ((*iter).plugin == plugin) { break; } } return iter; } } // namespace Akregator diff --git a/src/pluginmanager.h b/src/pluginmanager.h index adac17b6..09047169 100644 --- a/src/pluginmanager.h +++ b/src/pluginmanager.h @@ -1,112 +1,112 @@ /*************************************************************************** begin : 2004/03/12 copyright : (C) Mark Kretschmann email : markey@web.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef AKREGATOR_PLUGINMANAGER_H #define AKREGATOR_PLUGINMANAGER_H #include "akregatorpart_export.h" #include -#include +#include #include namespace Akregator { class Plugin; class AKREGATORPART_EXPORT PluginManager { public: /** * It will return a list of services that match your * specifications. The only required parameter is the service * type. This is something like 'text/plain' or 'text/html'. The * constraint parameter is used to limit the possible choices * returned based on the constraints you give it. * * The @p constraint language is rather full. The most common * keywords are AND, OR, NOT, IN, and EXIST, all used in an * almost spoken-word form. An example is: * \code * (Type == 'Service') and (('KParts/ReadOnlyPart' in ServiceTypes) or (exist Exec)) * \endcode * * The keys used in the query (Type, ServiceType, Exec) are all * fields found in the .desktop files. * * @param constraint A constraint to limit the choices returned, QString() to * get all services of the given @p servicetype * * @return A list of services that satisfy the query * @see http://developer.kde.org/documentation/library/kdeqt/tradersyntax.html */ static KService::List query(const QString &constraint = QString()); /** * Load and instantiate plugin from query * @param constraint A constraint to limit the choices returned, QString() to * get all services of the given @p servicetype * @return Pointer to Plugin, or NULL if error * @see http://developer.kde.org/documentation/library/kdeqt/tradersyntax.html */ static Akregator::Plugin *createFromQuery(const QString &constraint = QString()); /** * Load and instantiate plugin from service * @param service Pointer to KService * @param parent Parent object * @return Pointer to Plugin, or NULL if error */ static Akregator::Plugin *createFromService(const KService::Ptr &service, QObject *parent = nullptr); /** * Remove library and delete plugin * @param plugin Pointer to plugin */ static void unload(Akregator::Plugin *plugin); /** * Look up service for loaded plugin from store * @param plugin Pointer to plugin * @return KService, or 0 if not found */ static KService::Ptr getService(const Akregator::Plugin *plugin); /** * Dump properties from a service to stdout for debugging * @param service Pointer to KService */ static void dump(const KService::Ptr &service); /** * Show modal info dialog about plugin * @param constraint A constraint to limit the choices returned */ static void showAbout(const QString &constraint); private: struct StoreItem { Akregator::Plugin *plugin = nullptr; KService::Ptr service; }; static std::vector::iterator lookupPlugin(const Akregator::Plugin *plugin); //attributes: static std::vector m_store; }; } // namespace Akregator #endif // AKREGATOR_PLUGINMANAGER_H diff --git a/src/tabwidget.cpp b/src/tabwidget.cpp index b8223995..64eb482c 100644 --- a/src/tabwidget.cpp +++ b/src/tabwidget.cpp @@ -1,520 +1,520 @@ /* This file is part of Akregator. Copyright (C) 2004 Sashmit Bhaduri This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "tabwidget.h" #include #include #include #include #include #include #include #include #include "akregator_debug.h" #include #include -#include +#include #include -#include +#include #include #include #include "actionmanager.h" #include "akregatorconfig.h" #include "frame.h" #include "framemanager.h" #include "kernel.h" #include "openurlrequest.h" #include "utils/temporaryvalue.h" using namespace Akregator; class Q_DECL_HIDDEN TabWidget::Private { private: TabWidget *const q; public: explicit Private(TabWidget *qq) : q(qq) { } QHash frames; QHash framesById; int currentMaxLength = 30; QWidget *currentItem = nullptr; QToolButton *tabsClose = nullptr; QWidget *selectedWidget() const { return (currentItem && q->indexOf(currentItem) != -1) ? currentItem : q->currentWidget(); } int tabBarWidthForMaxChars(int maxLength); void setTitle(const QString &title, QWidget *sender); void updateTabBarVisibility(); Frame *currentFrame(); }; void TabWidget::Private::updateTabBarVisibility() { const bool tabBarIsHidden = ((q->count() <= 1) && !Settings::alwaysShowTabBar()); if (tabBarIsHidden) { q->tabBar()->hide(); } else { q->tabBar()->show(); } if (q->count() >= 1 && Settings::closeButtonOnTabs()) { q->tabBar()->tabButton(0, QTabBar::RightSide)->hide(); } } TabWidget::TabWidget(QWidget *parent) : QTabWidget(parent) , d(new Private(this)) { setMinimumSize(250, 150); setMovable(false); setDocumentMode(true); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &TabWidget::customContextMenuRequested, this, &TabWidget::slotTabContextMenuRequest); connect(this, &TabWidget::currentChanged, this, &TabWidget::slotTabChanged); connect(this, &QTabWidget::tabCloseRequested, this, &TabWidget::slotCloseRequest); setTabsClosable(Settings::closeButtonOnTabs()); d->tabsClose = new QToolButton(this); connect(d->tabsClose, &QToolButton::clicked, this, &TabWidget::slotRemoveCurrentFrame); d->tabsClose->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); d->tabsClose->setEnabled(false); d->tabsClose->adjustSize(); d->tabsClose->setToolTip(i18n("Close the current tab")); #ifndef QT_NO_ACCESSIBILITY d->tabsClose->setAccessibleName(i18n("Close tab")); #endif setCornerWidget(d->tabsClose, Qt::TopRightCorner); d->updateTabBarVisibility(); } TabWidget::~TabWidget() { delete d; } void TabWidget::slotTabContextMenuRequest(const QPoint &pos) { QTabBar *bar = tabBar(); if (count() <= 1) { return; } const int indexBar = bar->tabAt(bar->mapFrom(this, pos)); if (indexBar == -1) { return; } QMenu menu(this); const int countTab = (count() > 1); QAction *detachTab = menu.addAction(i18nc("@action:inmenu", "Detach Tab")); detachTab->setEnabled((indexBar != 0) && countTab); detachTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); menu.addSeparator(); QAction *closeTab = menu.addAction(i18nc("@action:inmenu", "Close Tab")); closeTab->setEnabled((indexBar != 0) && countTab); closeTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); QAction *allOther = menu.addAction(i18nc("@action:inmenu", "Close All Other Tabs")); allOther->setEnabled(countTab); allOther->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other"))); QAction *allTab = menu.addAction(i18nc("@action:inmenu", "Close All Tabs")); allTab->setEnabled(countTab); allTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); QAction *action = menu.exec(mapToGlobal(pos)); if (action == allOther) { // Close all other tabs slotCloseAllTabExcept(indexBar); } else if (action == closeTab) { slotCloseRequest(indexBar); } else if (action == allTab) { slotCloseAllTab(); } else if (action == detachTab) { slotDetachTab(indexBar); } } void TabWidget::closeAllTabExcept(int index) { //Don't close first tab for (int i = count() - 1; i > 0; --i) { if (i == index) { continue; } slotCloseRequest(i); } } void TabWidget::slotCloseAllTabExcept(int index) { closeAllTabExcept(index); } void TabWidget::slotCloseAllTab() { closeAllTabExcept(); } void TabWidget::slotSettingsChanged() { if (tabsClosable() != Settings::closeButtonOnTabs()) { setTabsClosable(Settings::closeButtonOnTabs()); } d->updateTabBarVisibility(); } void TabWidget::slotNextTab() { setCurrentIndex((currentIndex() + 1) % count()); } void TabWidget::slotPreviousTab() { if (currentIndex() == 0) { setCurrentIndex(count() - 1); } else { setCurrentIndex(currentIndex() - 1); } } void TabWidget::slotSelectFrame(int frameId) { Frame *frame = d->framesById.value(frameId); if (frame && frame != d->currentFrame()) { setCurrentWidget(frame); frame->setFocus(); } } void TabWidget::slotAddFrame(Frame *frame) { if (!frame) { return; } d->frames.insert(frame, frame); d->framesById.insert(frame->id(), frame); addTab(frame, frame->title()); connect(frame, &Frame::signalTitleChanged, this, &TabWidget::slotSetTitle); slotSetTitle(frame, frame->title()); } Frame *TabWidget::Private::currentFrame() { QWidget *w = q->currentWidget(); Q_ASSERT(frames.value(w)); return w ? frames.value(w) : nullptr; } void TabWidget::slotZoomChanged(qreal value) { if (!d->currentFrame()) { return; } Q_EMIT signalZoomChangedInFrame(d->currentFrame()->id(), value); } void TabWidget::slotTabChanged(int index) { Frame *frame = d->frames.value(widget(index)); d->tabsClose->setEnabled(frame && frame->isRemovable()); Q_EMIT signalCurrentFrameChanged(frame ? frame->id() : -1); } void TabWidget::tabInserted(int) { d->updateTabBarVisibility(); } void TabWidget::tabRemoved(int) { d->updateTabBarVisibility(); } void TabWidget::slotRemoveCurrentFrame() { Frame *const frame = d->currentFrame(); if (frame) { Q_EMIT signalRemoveFrameRequest(frame->id()); } } void TabWidget::slotRemoveFrame(int frameId) { if (!d->framesById.contains(frameId)) { return; } Frame *f = d->framesById.value(frameId); d->frames.remove(f); d->framesById.remove(frameId); f->disconnect(this); removeTab(indexOf(f)); Q_EMIT signalRemoveFrameRequest(f->id()); if (d->currentFrame()) { d->setTitle(d->currentFrame()->title(), currentWidget()); } } // copied wholesale from KonqFrameTabs int TabWidget::Private::tabBarWidthForMaxChars(int maxLength) { int hframe; QStyleOption o; hframe = q->tabBar()->style()->pixelMetric(QStyle::PM_TabBarTabHSpace, &o, q); QFontMetrics fm = q->tabBar()->fontMetrics(); int x = 0; for (int i = 0; i < q->count(); ++i) { Frame *f = frames.value(q->widget(i)); if (!f) { continue; // frames is out of sync, e.g. because tabInserted wasn't called yet - #185597 } QString newTitle = f->title(); if (newTitle.length() > maxLength) { newTitle = newTitle.left(maxLength - 3) + QLatin1String("..."); } int lw = fm.boundingRect(newTitle).width(); int iw = q->tabBar()->tabIcon(i).pixmap(q->tabBar()->style()->pixelMetric( QStyle::PM_SmallIconSize), QIcon::Normal ).width() + 4; x += (q->tabBar()->style()->sizeFromContents(QStyle::CT_TabBarTab, &o, QSize(qMax(lw + hframe + iw, QApplication::globalStrut().width()), 0), q)).width(); } return x; } void TabWidget::slotSetTitle(Frame *frame, const QString &title) { d->setTitle(title, frame); } void TabWidget::slotWebPageMutedOrAudibleChanged(Akregator::Frame *frame, bool isAudioMuted, bool wasRecentlyAudible) { Q_UNUSED(wasRecentlyAudible); const int idx = indexOf(frame); if (idx < 0) { return; } if (isAudioMuted) { setTabIcon(idx, QIcon::fromTheme(QStringLiteral("audio-volume-muted"))); } else { setTabIcon(idx, frame->icon()); } } void TabWidget::slotSetIcon(Akregator::Frame *frame, const QIcon &icon) { const int idx = indexOf(frame); if (idx < 0) { return; } frame->setIcon(icon); setTabIcon(idx, icon); } void TabWidget::Private::setTitle(const QString &title, QWidget *sender) { int senderIndex = q->indexOf(sender); q->setTabToolTip(senderIndex, QString()); int lcw = 0, rcw = 0; int tabBarHeight = q->tabBar()->sizeHint().height(); QWidget *leftCorner = q->cornerWidget(Qt::TopLeftCorner); if (leftCorner && leftCorner->isVisible()) { lcw = qMax(leftCorner->width(), tabBarHeight); } QWidget *rightCorner = q->cornerWidget(Qt::TopRightCorner); if (rightCorner && rightCorner->isVisible()) { rcw = qMax(rightCorner->width(), tabBarHeight); } int maxTabBarWidth = q->width() - lcw - rcw; int newMaxLength = 30; for (; newMaxLength > 3; newMaxLength--) { if (tabBarWidthForMaxChars(newMaxLength) < maxTabBarWidth) { break; } } QString newTitle = title; if (newTitle.length() > newMaxLength) { q->setTabToolTip(senderIndex, newTitle); newTitle = newTitle.left(newMaxLength - 3) + QLatin1String("..."); } newTitle.replace(QLatin1Char('&'), QStringLiteral("&&")); if (q->tabText(senderIndex) != newTitle) { q->setTabText(senderIndex, newTitle); } if (newMaxLength != currentMaxLength) { for (int i = 0; i < q->count(); ++i) { Frame *f = frames.value(q->widget(i)); if (!f) { continue; // frames is out of sync, e.g. because tabInserted wasn't called yet - #185597 } newTitle = f->title(); int index = q->indexOf(q->widget(i)); q->setTabToolTip(index, QString()); if (newTitle.length() > newMaxLength) { q->setTabToolTip(index, newTitle); newTitle = newTitle.left(newMaxLength - 3) + QLatin1String("..."); } newTitle.replace(QLatin1Char('&'), QStringLiteral("&&")); if (newTitle != q->tabText(index)) { q->setTabText(index, newTitle); } } currentMaxLength = newMaxLength; } } void TabWidget::slotDetachTab(int index) { QWidget *w = widget(index); Frame *frame = d->frames.value(w); if (frame && frame->url().isValid() && frame->isRemovable()) { OpenUrlRequest request; request.setUrl(frame->url()); request.setOptions(OpenUrlRequest::ExternalBrowser); Q_EMIT signalOpenUrlRequest(request); slotCloseRequest(index); } } void TabWidget::slotTextToSpeech() { Q_EMIT signalTextToSpeechInFrame(d->currentFrame()->id()); } void TabWidget::slotFindTextInHtml() { Q_EMIT signalFindTextInFrame(d->currentFrame()->id()); } void TabWidget::slotCopyLinkAddress() { Q_EMIT signalCopyLinkAsInFrame(d->currentFrame()->id()); } void TabWidget::slotSaveLinkAs() { Q_EMIT signalSaveLinkAsInFrame(d->currentFrame()->id()); } void TabWidget::slotPrintPreview() { Q_EMIT signalPrintPreviewInFrame(d->currentFrame()->id()); } void TabWidget::slotPrint() { Q_EMIT signalPrintInFrame(d->currentFrame()->id()); } void TabWidget::slotCopy() { Q_EMIT signalCopyInFrame(d->currentFrame()->id()); } void TabWidget::slotSaveImageOnDisk() { Q_EMIT signalSaveImageOnDisk(d->currentFrame()->id()); } void TabWidget::slotUnMute() { Q_EMIT signalMute(d->currentFrame()->id(), false); } void TabWidget::slotMute() { Q_EMIT signalMute(d->currentFrame()->id(), true); } void TabWidget::slotCopyImageLocation() { Q_EMIT signalCopyImageLocation(d->currentFrame()->id()); } void TabWidget::slotCloseTab() { QWidget *widget = d->selectedWidget(); Frame *frame = d->frames.value(widget); if (frame == nullptr || !frame->isRemovable()) { return; } Q_EMIT signalRemoveFrameRequest(frame->id()); } void TabWidget::slotReloadAllTabs() { for (Frame *frame : qAsConst(d->frames)) { frame->slotReload(); } } void TabWidget::slotCloseRequest(int index) { QWidget *w = widget(index); if (d->frames.value(w)) { Q_EMIT signalRemoveFrameRequest(d->frames.value(w)->id()); } } void TabWidget::slotActivateTab() { setCurrentIndex(sender()->objectName().rightRef(2).toInt() - 1); } diff --git a/src/utils.cpp b/src/utils.cpp index 60b8e166..51bf041b 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,48 +1,48 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "utils.h" #include #include #include -#include +#include using namespace Akregator; QString Utils::convertHtmlTags(const QString &title) { QTextDocument newText; newText.setHtml(title); return newText.toPlainText(); } QString Utils::stripTags(QString str) { return str.remove(QRegExp(QLatin1String("<[^>]*>"))); } uint Utils::calcHash(const QString &str) { const QByteArray array = str.toLatin1(); return qChecksum(array.constData(), array.size()); } diff --git a/src/widgets/searchbar.cpp b/src/widgets/searchbar.cpp index 4fd6b1bf..73dd2536 100644 --- a/src/widgets/searchbar.cpp +++ b/src/widgets/searchbar.cpp @@ -1,205 +1,205 @@ /* This file is part of Akregator. Copyright (C) 2005 Frank Osterfeld This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "searchbar.h" #include "akregatorconfig.h" #include "article.h" #include "widgets/statussearchline.h" -#include +#include #include #include #include #include #include using namespace Akregator; using namespace Akregator::Filters; SearchBar::SearchBar(QWidget *parent) : QWidget(parent) { m_delay = 400; QHBoxLayout *layout = new QHBoxLayout(this); layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(5); setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); m_searchLine = new StatusSearchLine(this); m_searchLine->setClearButtonEnabled(true); m_searchLine->setPlaceholderText(i18n("Search articles...")); layout->addWidget(m_searchLine); connect(m_searchLine, &KLineEdit::textChanged, this, &SearchBar::slotSearchStringChanged); connect(m_searchLine, &StatusSearchLine::forceLostFocus, this, &SearchBar::forceLostFocus); connect(m_searchLine, &StatusSearchLine::statusChanged, this, &SearchBar::slotStatusChanged); connect(&(m_timer), &QTimer::timeout, this, &SearchBar::slotActivateSearch); m_timer.setSingleShot(true); } SearchBar::~SearchBar() { } QString SearchBar::text() const { return m_searchText; } StatusSearchLine::Status SearchBar::status() const { return m_searchLine->status(); } void SearchBar::setDelay(int ms) { m_delay = ms; } int SearchBar::delay() const { return m_delay; } void SearchBar::setFocusSearchLine() { m_searchLine->setFocus(); } void SearchBar::slotClearSearch() { if (status() != Akregator::StatusSearchLine::AllArticles || !m_searchLine->text().trimmed().isEmpty()) { m_searchLine->clear(); m_searchLine->setStatus(Akregator::StatusSearchLine::AllArticles); m_timer.stop(); slotStopActiveSearch(); } } void SearchBar::slotSetStatus(int status) { m_searchLine->setStatus(static_cast(status)); triggerTimer(); } void SearchBar::slotSetText(const QString &text) { m_searchLine->setText(text); triggerTimer(); } void SearchBar::slotStatusChanged(Akregator::StatusSearchLine::Status /*status*/) { triggerTimer(); } std::vector > SearchBar::matchers() const { return m_matchers; } void SearchBar::updateQuickSearchLineText(const QString &searchLineShortcut) { m_searchLine->setPlaceholderText(i18n("Search articles...<%1>", searchLineShortcut)); } void SearchBar::slotSearchStringChanged(const QString &search) { m_searchText = search; triggerTimer(); } void SearchBar::slotStopActiveSearch() { std::vector > matchers; Settings::setStatusFilter(m_searchLine->status()); Settings::setTextFilter(m_searchText); m_matchers = matchers; Q_EMIT signalSearch(matchers); } void SearchBar::slotActivateSearch() { QVector textCriteria; QVector statusCriteria; if (!m_searchText.isEmpty()) { Criterion subjCrit(Criterion::Title, Criterion::Contains, m_searchText); textCriteria << subjCrit; Criterion crit1(Criterion::Description, Criterion::Contains, m_searchText); textCriteria << crit1; Criterion authCrit(Criterion::Author, Criterion::Contains, m_searchText); textCriteria << authCrit; } switch (m_searchLine->status()) { case StatusSearchLine::AllArticles: break; case StatusSearchLine::NewArticles: { Criterion crit(Criterion::Status, Criterion::Equals, New); statusCriteria << crit; break; } case StatusSearchLine::UnreadArticles: { Criterion crit1(Criterion::Status, Criterion::Equals, New); Criterion crit2(Criterion::Status, Criterion::Equals, Unread); statusCriteria << crit1; statusCriteria << crit2; break; } case StatusSearchLine::ImportantArticles: { Criterion crit(Criterion::KeepFlag, Criterion::Equals, true); statusCriteria << crit; break; } } std::vector > matchers; if (!textCriteria.isEmpty()) { matchers.push_back(QSharedPointer(new ArticleMatcher(textCriteria, ArticleMatcher::LogicalOr))); } if (!statusCriteria.isEmpty()) { matchers.push_back(QSharedPointer(new ArticleMatcher(statusCriteria, ArticleMatcher::LogicalOr))); } Settings::setStatusFilter(m_searchLine->status()); Settings::setTextFilter(m_searchText); m_matchers = matchers; Q_EMIT signalSearch(matchers); } void Akregator::SearchBar::triggerTimer() { if (m_timer.isActive()) { m_timer.stop(); } m_timer.start(200); }