diff --git a/CMakeLists.txt b/CMakeLists.txt index e5a2a964..12a6dbbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,116 +1,116 @@ project(akregator) cmake_minimum_required(VERSION 3.1) set(KF5_VERSION "5.32.0") find_package(ECM ${KF5_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(ECMInstallIcons) include(ECMOptionalAddSubdirectory) include(ECMSetupVersion) include(ECMAddTests) include(ECMMarkNonGuiExecutable) include(GenerateExportHeader) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) include(FeatureSummary) include(CheckFunctionExists) include(ECMGeneratePriFile) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMAddAppIcon) include(ECMQtDeclareLoggingCategory) include(ECMCoverageOption) # Do NOT add quote set(KDEPIM_DEV_VERSION alpha1) # add an extra space if(DEFINED KDEPIM_DEV_VERSION) set(KDEPIM_DEV_VERSION " ${KDEPIM_DEV_VERSION}") endif() set(KDEPIM_VERSION_NUMBER "5.5.40") set(KDEPIM_VERSION "${KDEPIM_VERSION_NUMBER}${KDEPIM_DEV_VERSION}") set(KDEPIM_LIB_VERSION "${KDEPIM_VERSION_NUMBER}") set(KDEPIM_LIB_SOVERSION "5") set(QT_REQUIRED_VERSION "5.7.0") set(KONTACTINTERFACE_LIB_VERSION "5.5.40") set(KPIMTEXTEDIT_LIB_VERSION "5.5.40") set(LIBGRANTLEETHEME_LIB_VERSION_LIB "5.5.40") set(LIBKDEPIM_LIB_VERSION_LIB "5.5.40") set(LIBKLEO_LIB_VERSION_LIB "5.5.40") -set(MESSAGELIB_LIB_VERSION_LIB "5.5.40") +set(MESSAGELIB_LIB_VERSION_LIB "5.5.43") set(PIMCOMMON_LIB_VERSION_LIB "5.5.40") set(SYNDICATION_LIB_VERSION "5.5.40") find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test WebEngine WebEngineWidgets PrintSupport) find_package(Grantlee5 "5.1" CONFIG REQUIRED) # Find KF5 package find_package(KF5Crash ${KF5_VERSION} REQUIRED) find_package(KF5DocTools ${KF5_VERSION} REQUIRED) find_package(KF5KCMUtils ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5NotifyConfig ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Parts ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5TextEditor ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Notifications ${KF5_VERSION} CONFIG REQUIRED) # Find KdepimLibs Package find_package(KF5GrantleeTheme ${LIBGRANTLEETHEME_LIB_VERSION_LIB} CONFIG REQUIRED) find_package(KF5KontactInterface ${KONTACTINTERFACE_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Libkdepim ${LIBKDEPIM_LIB_VERSION_LIB} CONFIG REQUIRED) find_package(KF5Libkleo ${LIBKLEO_LIB_VERSION_LIB} CONFIG REQUIRED) find_package(KF5MessageViewer ${MESSAGELIB_LIB_VERSION_LIB} CONFIG REQUIRED) find_package(KF5PimTextEdit ${KPIMTEXTEDIT_LIB_VERSION} CONFIG REQUIRED) find_package(KF5Syndication ${SYNDICATION_LIB_VERSION} CONFIG REQUIRED) find_package(KF5WebEngineViewer ${MESSAGELIB_LIB_VERSION_LIB} CONFIG REQUIRED) # Fix plugin support for removing theses dependancies find_package(KF5AkonadiMime ${AKONADI_MIMELIB_VERSION} CONFIG REQUIRED) find_package(KF5PimCommonAkonadi ${PIMCOMMON_LIB_VERSION_LIB} CONFIG REQUIRED) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) add_definitions(-DQT_USE_QSTRINGBUILDER) include_directories(${akregator_SOURCE_DIR} ${akregator_BINARY_DIR}) include(CheckTypeSize) check_type_size("long" SIZEOF_LONG) if (Qt5WebEngineWidgets_VERSION VERSION_GREATER "5.7.99") set(WEBENGINEVIEWER_PRINT_SUPPORT true) endif() configure_file(config-akregator.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-akregator.h) add_definitions(-DTRANSLATION_DOMAIN=\"akregator\") add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) add_definitions( -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT ) add_subdirectory(export) add_subdirectory(interfaces) add_subdirectory(plugins) add_subdirectory(configuration) add_subdirectory(src) add_subdirectory(kontactplugin) add_subdirectory(kconf_update) install( FILES akregator.renamecategories akregator.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) add_subdirectory(doc) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if (${Qt5WebEngine_VERSION} GREATER "5.7.99") MESSAGE(STATUS "QtWebEngine greater or equal 5.8.0 provides printing support and dnd fix. It's a good idea to use it") else() if (${Qt5WebEngine_VERSION} GREATER "5.6.99") MESSAGE(STATUS "QtWebEngine 5.7.0 doesn't provide printing support. It has a lot of regression. Better to upgrade QtWebEngine to 5.8.0") endif() endif() diff --git a/src/articleviewer-ng/webengine/articleviewerwebengine.cpp b/src/articleviewer-ng/webengine/articleviewerwebengine.cpp index 90be4c36..5173cbbc 100644 --- a/src/articleviewer-ng/webengine/articleviewerwebengine.cpp +++ b/src/articleviewer-ng/webengine/articleviewerwebengine.cpp @@ -1,486 +1,487 @@ /* Copyright (C) 2015-2017 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 "articleviewerwebengine.h" #include "akregator_debug.h" #include "articleviewerwebenginepage.h" #include "webengine/urlhandlerwebenginemanager.h" #include #include "actionmanager.h" #include "akregatorconfig.h" #include "actions/actions.h" #include "urlhandler/webengine/urlhandlerwebengine.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 using namespace Akregator; ArticleViewerWebEngine::ArticleViewerWebEngine(KActionCollection *ac, QWidget *parent) : WebEngineViewer::WebEngineView(parent), mActionCollection(ac), mLastButtonClicked(LeftButton), mViewerPluginToolManager(nullptr) { mNetworkAccessManager = new WebEngineViewer::InterceptorManager(this, ac, this); QWebEngineProfile *profile = new QWebEngineProfile(this); mPageEngine = new ArticleViewerWebEnginePage(profile, this); profile->setPersistentCookiesPolicy(QWebEngineProfile::ForcePersistentCookies); setPage(mPageEngine); connect(this, &ArticleViewerWebEngine::showContextMenu, this, &ArticleViewerWebEngine::slotShowContextMenu); setFocusPolicy(Qt::WheelFocus); connect(mPageEngine, &ArticleViewerWebEnginePage::urlClicked, this, &ArticleViewerWebEngine::slotLinkClicked); mWebEngineViewAccessKey = new WebEngineViewer::WebEngineAccessKey(this, this); mWebEngineViewAccessKey->setActionCollection(mActionCollection); connect(mWebEngineViewAccessKey, &WebEngineViewer::WebEngineAccessKey::openUrl, this, &ArticleViewerWebEngine::slotLinkClicked); connect(this, &ArticleViewerWebEngine::loadStarted, this, &ArticleViewerWebEngine::slotLoadStarted); connect(this, &ArticleViewerWebEngine::loadFinished, this, &ArticleViewerWebEngine::slotLoadFinished); connect(page(), &QWebEnginePage::linkHovered, this, &ArticleViewerWebEngine::slotLinkHovered); setContextMenuPolicy(Qt::DefaultContextMenu); mWebShortcutMenuManager = new KIO::KUriFilterSearchProviderActions(this); mShareServiceManager = new PimCommon::ShareServiceUrlManager(this); connect(mShareServiceManager, &PimCommon::ShareServiceUrlManager::serviceUrlSelected, this, &ArticleViewerWebEngine::slotServiceUrlSelected); connect(page(), &QWebEnginePage::audioMutedChanged, this, &ArticleViewerWebEngine::slotWebPageMutedOrAudibleChanged); connect(page(), &QWebEnginePage::recentlyAudibleChanged, this, &ArticleViewerWebEngine::slotWebPageMutedOrAudibleChanged); - connect(WebEngineViewer::LocalDataBaseManager::self(), &WebEngineViewer::LocalDataBaseManager::checkUrlFinished, this, &ArticleViewerWebEngine::slotCheckedUrlFinished); + connect(phishingDatabase(), &WebEngineViewer::LocalDataBaseManager::checkUrlFinished, + this, &ArticleViewerWebEngine::slotCheckedUrlFinished); } ArticleViewerWebEngine::~ArticleViewerWebEngine() { } void ArticleViewerWebEngine::slotWebPageMutedOrAudibleChanged() { Q_EMIT webPageMutedOrAudibleChanged(page()->isAudioMuted(), page()->recentlyAudible()); } QVariantHash ArticleViewerWebEngine::introductionData() const { QVariantHash data; data[QStringLiteral("icon")] = QStringLiteral("akregator"); data[QStringLiteral("name")] = i18n("Akregator"); data[QStringLiteral("subtitle")] = i18n("Akregator is a KDE news feed reader."); data[QStringLiteral("version")] = KAboutData::applicationData().version(); return data; } void ArticleViewerWebEngine::showAboutPage() { paintAboutScreen(QStringLiteral("introduction_akregator.html"), introductionData()); } void ArticleViewerWebEngine::paintAboutScreen(const QString &templateName, const QVariantHash &data) { GrantleeTheme::ThemeManager manager(QStringLiteral("splashPage"), QStringLiteral("splash.theme"), nullptr, QStringLiteral("messageviewer/about/")); GrantleeTheme::Theme theme = manager.theme(QStringLiteral("default")); if (theme.isValid()) { setHtml(theme.render(templateName, data, QByteArrayLiteral("akregator")), QUrl::fromLocalFile(theme.absolutePath() + QLatin1Char('/'))); } else { qCDebug(AKREGATOR_LOG) << "Theme error: failed to find splash theme"; } } void ArticleViewerWebEngine::slotServiceUrlSelected(PimCommon::ShareServiceUrlManager::ServiceType type) { const QUrl url = mShareServiceManager->generateServiceUrl(mCurrentUrl.url(), QString(), type); mShareServiceManager->openUrl(url); } void ArticleViewerWebEngine::slotSaveLinkAs() { QUrl tmp(mCurrentUrl); if (tmp.fileName().isEmpty()) { tmp = tmp.adjusted(QUrl::RemoveFilename); tmp.setPath(tmp.path() + QLatin1String("index.html")); } KParts::BrowserRun::simpleSave(tmp, tmp.fileName()); } void ArticleViewerWebEngine::slotSaveImageOnDiskInFrame() { slotSaveLinkAs(); } void ArticleViewerWebEngine::slotCopyImageLocationInFrame() { slotCopyLinkAddress(); } void ArticleViewerWebEngine::slotMute(bool mute) { page()->setAudioMuted(mute); } void ArticleViewerWebEngine::slotCopyLinkAddress() { if (mCurrentUrl.isEmpty()) { return; } QClipboard *cb = QApplication::clipboard(); cb->setText(mCurrentUrl.toString(), QClipboard::Clipboard); // don't set url to selection as it's a no-no according to a fd.o spec // which spec? Nobody seems to care (tested Firefox (3.5.10) Konqueror,and KMail (4.2.3)), so I re-enable the following line unless someone gives // a good reason to remove it again (bug 183022) --Frank cb->setText(mCurrentUrl.toString(), QClipboard::Selection); } void ArticleViewerWebEngine::contextMenuEvent(QContextMenuEvent *e) { displayContextMenu(e->pos()); } void ArticleViewerWebEngine::slotShowContextMenu(const QPoint &pos) { displayContextMenu(pos); } void ArticleViewerWebEngine::slotCopy() { triggerPageAction(QWebEnginePage::Copy); } void ArticleViewerWebEngine::slotLoadFinished() { restoreCurrentPosition(); unsetCursor(); clearRelativePosition(); } void ArticleViewerWebEngine::slotLoadStarted() { mWebEngineViewAccessKey->hideAccessKeys(); setCursor(Qt::WaitCursor); } void ArticleViewerWebEngine::slotWebHitFinished(const WebEngineViewer::WebHitTestResult &result) { mCurrentUrl = result.linkUrl(); if (URLHandlerWebEngineManager::instance()->handleContextMenuRequest(mCurrentUrl, mapToGlobal(result.pos()), this)) { return; } QMenu popup(this); const bool contentSelected = !selectedText().isEmpty(); if (!contentSelected) { if (!mCurrentUrl.isEmpty()) { popup.addAction(createOpenLinkInNewTabAction(mCurrentUrl, this, SLOT(slotOpenLinkInForegroundTab()), &popup)); popup.addAction(createOpenLinkInExternalBrowserAction(mCurrentUrl, this, SLOT(slotOpenLinkInBrowser()), &popup)); popup.addSeparator(); popup.addAction(mActionCollection->action(QStringLiteral("savelinkas"))); popup.addAction(mActionCollection->action(QStringLiteral("copylinkaddress"))); } if (!result.imageUrl().isEmpty()) { popup.addSeparator(); popup.addAction(mActionCollection->action(QStringLiteral("copy_image_location"))); popup.addAction(mActionCollection->action(QStringLiteral("saveas_imageurl"))); } popup.addSeparator(); popup.addActions(viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedUrl)); popup.addSeparator(); popup.addAction(mShareServiceManager->menu()); } else { popup.addAction(ActionManager::getInstance()->action(QStringLiteral("viewer_copy"))); popup.addSeparator(); mWebShortcutMenuManager->setSelectedText(page()->selectedText()); mWebShortcutMenuManager->addWebShortcutsToMenu(&popup); popup.addSeparator(); popup.addActions(viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedSelection)); } popup.addSeparator(); popup.addAction(ActionManager::getInstance()->action(QStringLiteral("viewer_print"))); popup.addAction(ActionManager::getInstance()->action(QStringLiteral("viewer_printpreview"))); popup.addSeparator(); popup.addAction(ActionManager::getInstance()->action(QStringLiteral("tab_mute"))); popup.addAction(ActionManager::getInstance()->action(QStringLiteral("tab_unmute"))); popup.addSeparator(); popup.addAction(ActionManager::getInstance()->action(QStringLiteral("find_in_messages"))); if (KPIMTextEdit::TextToSpeech::self()->isReady()) { popup.addSeparator(); popup.addAction(ActionManager::getInstance()->action(QStringLiteral("speak_text"))); } popup.exec(mapToGlobal(result.pos())); } void ArticleViewerWebEngine::displayContextMenu(const QPoint &pos) { WebEngineViewer::WebHitTest *webHit = mPageEngine->hitTestContent(pos); connect(webHit, &WebEngineViewer::WebHitTest::finished, this, &ArticleViewerWebEngine::slotWebHitFinished); } void ArticleViewerWebEngine::slotLinkHovered(const QString &link) { QString msg = URLHandlerWebEngineManager::instance()->statusBarMessage(QUrl(link), this); if (msg.isEmpty()) { msg = link; } Q_EMIT showStatusBarMessage(msg); } void ArticleViewerWebEngine::forwardKeyReleaseEvent(QKeyEvent *e) { if (Settings::self()->accessKeyEnabled()) { mWebEngineViewAccessKey->keyReleaseEvent(e); } } void ArticleViewerWebEngine::forwardKeyPressEvent(QKeyEvent *e) { if (e && hasFocus()) { if (Settings::self()->accessKeyEnabled()) { mWebEngineViewAccessKey->keyPressEvent(e); } } } void ArticleViewerWebEngine::forwardWheelEvent(QWheelEvent *e) { if (Settings::self()->accessKeyEnabled()) { mWebEngineViewAccessKey->wheelEvent(e); } if (QApplication::keyboardModifiers() & Qt::ControlModifier) { const int numDegrees = e->delta() / 8; const int numSteps = numDegrees / 15; const qreal factor = ActionManager::getInstance()->zoomActionMenu()->zoomFactor() + numSteps * 10; if (factor >= 10 && factor <= 300) { ActionManager::getInstance()->zoomActionMenu()->setZoomFactor(factor); ActionManager::getInstance()->zoomActionMenu()->setWebViewerZoomFactor(factor / 100.0); } e->accept(); } } void ArticleViewerWebEngine::resizeEvent(QResizeEvent *e) { if (Settings::self()->accessKeyEnabled()) { mWebEngineViewAccessKey->resizeEvent(e); } QWebEngineView::resizeEvent(e); } void ArticleViewerWebEngine::disableIntroduction() { KGuiItem yesButton(KStandardGuiItem::yes()); yesButton.setText(i18n("Disable")); KGuiItem noButton(KStandardGuiItem::no()); noButton.setText(i18n("Keep Enabled")); if (KMessageBox::questionYesNo(this, i18n("Are you sure you want to disable this introduction page?"), i18n("Disable Introduction Page"), yesButton, noButton) == KMessageBox::Yes) { Settings::self()->setDisableIntroduction(true); Settings::self()->save(); } } void ArticleViewerWebEngine::setArticleAction(ArticleViewerWebEngine::ArticleAction type, const QString &articleId, const QString &feed) { Q_EMIT articleAction(type, articleId, feed); } void ArticleViewerWebEngine::restoreCurrentPosition() { mPageEngine->runJavaScript(WebEngineViewer::WebEngineScript::scrollToRelativePosition(relativePosition())); } void ArticleViewerWebEngine::forwardMouseReleaseEvent(QMouseEvent *event) { if (event->button() & Qt::RightButton) { Q_EMIT showContextMenu(event->pos()); mLastButtonClicked = RightButton; } else if (event->button() & Qt::MiddleButton) { mLastButtonClicked = MiddleButton; } else if (event->button() & Qt::LeftButton) { mLastButtonClicked = LeftButton; } } bool ArticleViewerWebEngine::urlIsAMalwareButContinue() { if (KMessageBox::No == KMessageBox::warningYesNo(this, i18n("This web site is a malware, do you want to continue to show it?"), i18n("Malware"))) { return false; } return true; } void ArticleViewerWebEngine::slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status) { switch (status) { case WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork: KMessageBox::error(this, i18n("The network is broken."), i18n("Check Phishing Url")); break; case WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl: KMessageBox::error(this, i18n("The url %1 is not valid.", url.toString()), i18n("Check Phishing Url")); break; case WebEngineViewer::CheckPhishingUrlUtil::Ok: break; case WebEngineViewer::CheckPhishingUrlUtil::MalWare: if (!urlIsAMalwareButContinue()) { return; } break; case WebEngineViewer::CheckPhishingUrlUtil::Unknown: qCWarning(AKREGATOR_LOG) << "ArticleViewerWebEngine::slotCheckedUrlFinished unknown error "; break; } openSafeUrl(url); } void ArticleViewerWebEngine::slotLinkClicked(const QUrl &url) { if (URLHandlerWebEngineManager::instance()->handleClick(url, this)) { return; } if (Settings::checkPhishingUrl()) { - WebEngineViewer::LocalDataBaseManager::self()->checkUrl(url); + phishingDatabase()->checkUrl(url); } else { openSafeUrl(url); } } void ArticleViewerWebEngine::openSafeUrl(const QUrl &url) { mCurrentUrl = url; OpenUrlRequest req(mCurrentUrl); if (mLastButtonClicked == LeftButton) { switch (Settings::lMBBehaviour()) { case Settings::EnumLMBBehaviour::OpenInExternalBrowser: req.setOptions(OpenUrlRequest::ExternalBrowser); break; case Settings::EnumLMBBehaviour::OpenInBackground: req.setOpenInBackground(true); req.setOptions(OpenUrlRequest::NewTab); break; default: break; } } else if (mLastButtonClicked == MiddleButton) { switch (Settings::mMBBehaviour()) { case Settings::EnumMMBBehaviour::OpenInExternalBrowser: req.setOptions(OpenUrlRequest::ExternalBrowser); break; case Settings::EnumMMBBehaviour::OpenInBackground: req.setOpenInBackground(true); req.setOptions(OpenUrlRequest::NewTab); break; default: break; } } Q_EMIT signalOpenUrlRequest(req); } void ArticleViewerWebEngine::slotOpenLinkInForegroundTab() { OpenUrlRequest req(mCurrentUrl); req.setOptions(OpenUrlRequest::NewTab); Q_EMIT signalOpenUrlRequest(req); } void ArticleViewerWebEngine::slotOpenLinkInBackgroundTab() { OpenUrlRequest req(mCurrentUrl); req.setOptions(OpenUrlRequest::NewTab); req.setOpenInBackground(true); Q_EMIT signalOpenUrlRequest(req); } void ArticleViewerWebEngine::slotOpenLinkInBrowser() { OpenUrlRequest req(mCurrentUrl); req.setOptions(OpenUrlRequest::ExternalBrowser); Q_EMIT signalOpenUrlRequest(req); } void ArticleViewerWebEngine::createViewerPluginToolManager(KActionCollection *ac, QWidget *parent) { mViewerPluginToolManager = new MessageViewer::ViewerPluginToolManager(parent, this); mViewerPluginToolManager->setActionCollection(ac); mViewerPluginToolManager->setPluginName(QStringLiteral("akregator")); mViewerPluginToolManager->setServiceTypeName(QStringLiteral("Akregator/ViewerPlugin")); if (!mViewerPluginToolManager->initializePluginList()) { qCWarning(AKREGATOR_LOG) << " Impossible to initialize plugins"; } mViewerPluginToolManager->createView(); connect(mViewerPluginToolManager, &MessageViewer::ViewerPluginToolManager::activatePlugin, this, &ArticleViewerWebEngine::slotActivatePlugin); } QList ArticleViewerWebEngine::viewerPluginActionList(MessageViewer::ViewerPluginInterface::SpecificFeatureTypes features) { if (mViewerPluginToolManager) { return mViewerPluginToolManager->viewerPluginActionList(features); } return QList(); } void ArticleViewerWebEngine::slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface) { const QString text = selectedText(); if (!text.isEmpty()) { interface->setText(text); } interface->setUrl(mCurrentUrl); interface->execute(); } diff --git a/src/mainwidget.cpp b/src/mainwidget.cpp index 8b40cf1f..39bc6cc6 100644 --- a/src/mainwidget.cpp +++ b/src/mainwidget.cpp @@ -1,1346 +1,1343 @@ /* 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 #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); - WebEngineViewer::LocalDataBaseManager::self()->initialize(); - FeedListManagementInterface::setInstance(m_feedListManagementInterface); m_actionManager->initMainWidget(this); m_part = part; m_shuttingDown = false; m_displayingAboutPage = false; setFocusPolicy(Qt::StrongFocus); QVBoxLayout *lt = new QVBoxLayout(this); lt->setMargin(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->setMargin(0); m_searchBar = new SearchBar(m_mainTab); if (!Settings::showQuickFilter()) { m_searchBar->hide(); } 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->setMargin(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); 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::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 = 0; 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, 0, 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, "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", 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; } case ArticleViewerWebEngine::Share: const Akregator::Article article = m_feedList->findArticle(feed, articleId); const QUrl url = article.link(); if (url.isValid()) { //TODO } break; } }