diff --git a/src/mainwidget.cpp b/src/mainwidget.cpp index 7a42d4f6..ffbadc2a 100644 --- a/src/mainwidget.cpp +++ b/src/mainwidget.cpp @@ -1,1345 +1,1351 @@ /* 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 "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", 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; } } diff --git a/src/mainwidget.h b/src/mainwidget.h index 51e7fda0..dc942c33 100644 --- a/src/mainwidget.h +++ b/src/mainwidget.h @@ -1,299 +1,300 @@ /* 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. */ #ifndef AKREGATOR_MAINWIDGET_H #define AKREGATOR_MAINWIDGET_H #include "akregatorpart_export.h" #include "articleviewer-ng/webengine/articleviewerwebenginewidgetng.h" #include "feed.h" #include #include #include #include class KConfig; class KConfigGroup; class QDomDocument; class QSplitter; namespace Akregator { class WebEngineFrame; class AbstractSelectionController; class ActionManagerImpl; class ArticleListView; class ArticleViewerWidget; class Folder; class FeedList; class FeedListManagementImpl; class Frame; class Part; class SearchBar; class SubscriptionListView; class TabWidget; class MainFrame; class DownloadArticleJob; /** * This is the main widget of the view, containing tree view, article list, viewer etc. */ class AKREGATORPART_EXPORT MainWidget : public QWidget { Q_OBJECT public: /** constructor @param part the Akregator::Part which contains this widget @param parent parent widget @param actionManager Akregator specific implementation of ActionManager @param name the name of the widget (@ref QWidget ) */ MainWidget(Akregator::Part *part, QWidget *parent, ActionManagerImpl *actionManager, const QString &name); /** destructor. Note that cleanups should be done in slotOnShutdown(), so we don't risk accessing self-deleting objects after deletion. */ ~MainWidget(); /** saves settings. Make sure that the Settings singleton is not destroyed yet when saveSettings is called */ void saveSettings(); /** Adds the feeds in @c doc to the "Imported Folder" @param doc the DOM tree (OPML) of the feeds to import */ void importFeedList(const QDomDocument &doc); /** * @return the displayed Feed List in OPML format */ QDomDocument feedListToOPML(); void setFeedList(const QSharedPointer &feedList); /** * Add a feed to a group. * @param url The URL of the feed to add. * @param group The name of the folder into which the feed is added. * If the group does not exist, it is created. * The feed is added as the last member of the group. */ void addFeedToGroup(const QString &url, const QString &group); QSharedPointer allFeedsList() { return m_feedList; } /** session management **/ void readProperties(const KConfigGroup &config); void saveProperties(KConfigGroup &config); //Returns true if networking is available bool isNetworkAvailable() const; enum ViewMode { NormalView = 0, WidescreenView, CombinedView }; ViewMode viewMode() const { return m_viewMode; } void currentArticleInfo(QString &link, QString &title); void updateQuickSearchLineText(); Q_SIGNALS: /** emitted when the unread count of "All Feeds" was changed */ void signalUnreadCountChanged(int); /** emitted when the articles selected changed */ void signalArticlesSelected(const QVector &); public Q_SLOTS: /** opens the current article (currentItem) in external browser TODO: use selected instead of current? */ void slotOpenSelectedArticlesInBrowser(); /** opens current article in new tab, background/foreground depends on settings TODO: use selected instead of current? */ void slotOpenSelectedArticles(); void slotOpenSelectedArticlesInBackground(); void slotOnShutdown(); /** selected tree node has changed */ void slotNodeSelected(Akregator::TreeNode *node); /** the article selection has changed */ void slotArticleSelected(const Akregator::Article &); void ensureArticleTabVisible(); /** emits @ref signalUnreadCountChanged(int) */ void slotSetTotalUnread(); /** copies the link of current article to clipboard */ void slotCopyLinkAddress(); void slotRequestNewFrame(int &frameId); /** adds a new feed to the feed tree */ void slotFeedAdd(); /** adds a feed group to the feed tree */ void slotFeedAddGroup(); /** removes the currently selected feed (ask for confirmation)*/ void slotFeedRemove(); /** calls the properties dialog for feeds, starts renaming for feed groups */ void slotFeedModify(); /** fetches the currently selected feed */ void slotFetchCurrentFeed(); /** starts fetching of all feeds in the tree */ void slotFetchAllFeeds(); /** marks all articles in the currently selected feed as read */ void slotMarkAllRead(); /** marks all articles in all feeds in the tree as read */ void slotMarkAllFeedsRead(); /** opens the homepage of the currently selected feed */ void slotOpenHomepage(); /** reloads all open tabs */ void slotReloadAllTabs(); /** toggles the keep flag of the currently selected article */ void slotArticleToggleKeepFlag(bool enabled); /** deletes the currently selected article */ void slotArticleDelete(); /** marks the currently selected article as read */ void slotSetSelectedArticleRead(); /** marks the currently selected article as unread */ void slotSetSelectedArticleUnread(); /** marks the currently selected article as new */ void slotSetSelectedArticleNew(); /** marks the currently selected article as read after a user-set delay */ void slotSetCurrentArticleReadDelayed(); /** switches view mode to normal view */ void slotNormalView(); /** switches view mode to widescreen view */ void slotWidescreenView(); /** switches view mode to combined view */ void slotCombinedView(); /** toggles the visibility of the filter bar */ void slotToggleShowQuickFilter(); /** selects the previous unread article in the article list */ void slotPrevUnreadArticle(); /** selects the next unread article in the article list */ void slotNextUnreadArticle(); void slotMoveCurrentNodeUp(); void slotMoveCurrentNodeDown(); void slotMoveCurrentNodeLeft(); void slotMoveCurrentNodeRight(); void slotSendLink(); void slotSendFile(); void slotNetworkStatusChanged(bool status); void slotFocusQuickSearch(); protected: void sendArticle(bool attach = false); void addFeed(const QString &url, TreeNode *after, Folder *parent, bool autoExec = true); protected Q_SLOTS: /** special behaviour in article list view (TODO: move code there?) */ void slotMouseButtonPressed(int button, const QUrl &); /** opens the link of an article in the external browser */ void slotOpenArticleInBrowser(const Akregator::Article &article); void slotDoIntervalFetches(); void slotDeleteExpiredArticles(); void slotFetchingStarted(); void slotFetchingStopped(); void slotFramesChanged(); private Q_SLOTS: void slotShowStatusBarMessage(const QString &msg); void slotCurrentFrameChanged(int frameId); void slotArticleAction(Akregator::ArticleViewerWebEngine::ArticleAction type, const QString &articleId, const QString &feed); void slotSettingsChanged(); private: + void slotSetFocusToViewer(); void sendArticle(const QByteArray &text, const QString &title, bool attach); void deleteExpiredArticles(const QSharedPointer &feedList); void connectFrame(Akregator::WebEngineFrame *frame); void cleanUpDownloadFile(); /** opens current article in new tab, background/foreground depends on settings TODO: use selected instead of current? */ void openSelectedArticles(bool openInBackground); AbstractSelectionController *m_selectionController; QSharedPointer m_feedList; SubscriptionListView *m_feedListView = nullptr; ArticleListView *m_articleListView = nullptr; ArticleViewerWidget *m_articleViewer = nullptr; TabWidget *m_tabWidget = nullptr; QWidget *m_mainTab = nullptr; MainFrame *m_mainFrame = nullptr; SearchBar *m_searchBar = nullptr; QSplitter *m_articleSplitter = nullptr; QSplitter *m_horizontalSplitter = nullptr; Akregator::Part *m_part = nullptr; ViewMode m_viewMode; QTimer *m_fetchTimer = nullptr; QTimer *m_expiryTimer = nullptr; QTimer *m_markReadTimer = nullptr; bool m_shuttingDown = false; bool m_displayingAboutPage = false; ActionManagerImpl *m_actionManager = nullptr; FeedListManagementImpl *const m_feedListManagementInterface = nullptr; QWidget *m_articleWidget = nullptr; QList > mListDownloadArticleJobs; }; } // namespace Akregator #endif // AKREGATOR_MAINWIDGET_H diff --git a/src/widgets/searchbar.cpp b/src/widgets/searchbar.cpp index a89b37b0..2be3e683 100644 --- a/src/widgets/searchbar.cpp +++ b/src/widgets/searchbar.cpp @@ -1,216 +1,217 @@ /* 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 "articlematcher.h" #include "article.h" #include "widgets/statussearchline.h" #include #include #include #include #include #include #include using namespace Akregator; using namespace Akregator::Filters; class SearchBar::SearchBarPrivate { public: QString searchText; QTimer timer; StatusSearchLine *searchLine = nullptr; int delay; std::vector > matchers; void triggerTimer() { if (timer.isActive()) { timer.stop(); } timer.start(200); } }; SearchBar::SearchBar(QWidget *parent) : QWidget(parent) , d(new SearchBar::SearchBarPrivate) { d->delay = 400; QHBoxLayout *layout = new QHBoxLayout(this); layout->setMargin(2); layout->setSpacing(5); setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); d->searchLine = new StatusSearchLine(this); d->searchLine->setClearButtonEnabled(true); d->searchLine->setPlaceholderText(i18n("Search articles...")); layout->addWidget(d->searchLine); connect(d->searchLine, &KLineEdit::textChanged, this, &SearchBar::slotSearchStringChanged); + connect(d->searchLine, &StatusSearchLine::forceLostFocus, this, &SearchBar::forceLostFocus); connect(d->searchLine, &StatusSearchLine::statusChanged, this, &SearchBar::slotStatusChanged); connect(&(d->timer), &QTimer::timeout, this, &SearchBar::slotActivateSearch); d->timer.setSingleShot(true); } SearchBar::~SearchBar() { delete d; d = nullptr; } QString SearchBar::text() const { return d->searchText; } int SearchBar::status() const { return static_cast(d->searchLine->status()); } void SearchBar::setDelay(int ms) { d->delay = ms; } int SearchBar::delay() const { return d->delay; } void SearchBar::setFocusSearchLine() { d->searchLine->setFocus(); } void SearchBar::slotClearSearch() { if (status() != 0 || !d->searchLine->text().trimmed().isEmpty()) { d->searchLine->clear(); d->searchLine->setStatus(Akregator::StatusSearchLine::AllArticles); d->timer.stop(); slotStopActiveSearch(); } } void SearchBar::slotSetStatus(int status) { d->searchLine->setStatus(static_cast(status)); d->triggerTimer(); } void SearchBar::slotSetText(const QString &text) { d->searchLine->setText(text); d->triggerTimer(); } void SearchBar::slotStatusChanged(Akregator::StatusSearchLine::Status /*status*/) { d->triggerTimer(); } std::vector > SearchBar::matchers() const { return d->matchers; } void SearchBar::updateQuickSearchLineText(const QString &searchLineShortcut) { d->searchLine->setPlaceholderText(i18n("Search articles...<%1>", searchLineShortcut)); } void SearchBar::slotSearchStringChanged(const QString &search) { d->searchText = search; d->triggerTimer(); } void SearchBar::slotStopActiveSearch() { std::vector > matchers; Settings::setStatusFilter(d->searchLine->status()); Settings::setTextFilter(d->searchText); d->matchers = matchers; Q_EMIT signalSearch(matchers); } void SearchBar::slotActivateSearch() { QVector textCriteria; QVector statusCriteria; if (!d->searchText.isEmpty()) { Criterion subjCrit(Criterion::Title, Criterion::Contains, d->searchText); textCriteria << subjCrit; Criterion crit1(Criterion::Description, Criterion::Contains, d->searchText); textCriteria << crit1; Criterion authCrit(Criterion::Author, Criterion::Contains, d->searchText); textCriteria << authCrit; } switch (d->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(d->searchLine->status()); Settings::setTextFilter(d->searchText); d->matchers = matchers; Q_EMIT signalSearch(matchers); } diff --git a/src/widgets/searchbar.h b/src/widgets/searchbar.h index 641d63de..197f851e 100644 --- a/src/widgets/searchbar.h +++ b/src/widgets/searchbar.h @@ -1,79 +1,80 @@ /* 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. */ #ifndef AKREGATOR_SEARCHBAR_H #define AKREGATOR_SEARCHBAR_H #include #include #include #include "widgets/statussearchline.h" namespace Akregator { namespace Filters { class AbstractMatcher; } class SearchBar : public QWidget { Q_OBJECT public: explicit SearchBar(QWidget *parent = nullptr); ~SearchBar(); QString text() const; int status() const; void setDelay(int ms); int delay() const; void setFocusSearchLine(); std::vector > matchers() const; void updateQuickSearchLineText(const QString &searchLine); Q_SIGNALS: /** emitted when the text and status filters were updated. Params are textfilter, statusfilter */ void signalSearch(const std::vector > &); + void forceLostFocus(); public Q_SLOTS: void slotClearSearch(); void slotSetStatus(int status); void slotSetText(const QString &text); private Q_SLOTS: void slotSearchStringChanged(const QString &search); void slotStopActiveSearch(); void slotActivateSearch(); void slotStatusChanged(Akregator::StatusSearchLine::Status); private: class SearchBarPrivate; SearchBarPrivate *d; }; } // namespace Akregator #endif //AKREGATOR_SEARCHBAR_H diff --git a/src/widgets/statussearchline.cpp b/src/widgets/statussearchline.cpp index 208964ca..19add134 100644 --- a/src/widgets/statussearchline.cpp +++ b/src/widgets/statussearchline.cpp @@ -1,107 +1,117 @@ /* Copyright (C) 2016-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 "statussearchline.h" #include #include #include +#include #include using namespace Akregator; Q_DECLARE_METATYPE(Akregator::StatusSearchLine::Status) StatusSearchLine::StatusSearchLine(QWidget *parent) : KLineEdit(parent) , mDefaultStatus(AllArticles) { initializeHash(); initializeActions(); } StatusSearchLine::~StatusSearchLine() { } +void StatusSearchLine::keyPressEvent(QKeyEvent *e) +{ + if (e->key() == Qt::Key_Escape) { + Q_EMIT forceLostFocus(); + } else { + KLineEdit::keyPressEvent(e); + } +} + void StatusSearchLine::initializeHash() { const QIcon iconAll = QIcon::fromTheme(QStringLiteral("system-run")); const QIcon iconNew = QIcon::fromTheme(QStringLiteral("mail-mark-unread-new")); const QIcon iconUnread = QIcon::fromTheme(QStringLiteral("mail-mark-unread")); const QIcon iconKeep = QIcon::fromTheme(QStringLiteral("mail-mark-important")); StatusInfo statusAll(i18n("All Articles"), iconAll); StatusInfo statusUnread(i18nc("Unread articles filter", "Unread"), iconUnread); StatusInfo statusNew(i18nc("New articles filter", "New"), iconNew); StatusInfo statusImportant(i18nc("Important articles filter", "Important"), iconKeep); mHashStatus.insert(AllArticles, statusAll); mHashStatus.insert(NewArticles, statusNew); mHashStatus.insert(UnreadArticles, statusUnread); mHashStatus.insert(ImportantArticles, statusImportant); } void StatusSearchLine::setStatus(StatusSearchLine::Status status) { updateStatusIcon(status); } void StatusSearchLine::initializeActions() { mSearchLineStatusAction = addAction(mHashStatus.value(AllArticles).mIcon, QLineEdit::LeadingPosition); mSearchLineStatusAction->setToolTip(mHashStatus.value(AllArticles).mText); connect(mSearchLineStatusAction, &QAction::triggered, this, &StatusSearchLine::showMenu); } void StatusSearchLine::showMenu() { QMenu p(this); QActionGroup *grp = new QActionGroup(this); grp->setExclusive(true); for (int i = AllArticles; i <= ImportantArticles; ++i) { StatusSearchLine::Status status = static_cast(i); QAction *act = new QAction(mHashStatus.value(status).mIcon, mHashStatus.value(status).mText, this); act->setCheckable(true); act->setChecked(mDefaultStatus == status); act->setData(QVariant::fromValue(status)); grp->addAction(act); p.addAction(act); if (i == AllArticles) { p.addSeparator(); } } QAction *a = p.exec(mapToGlobal(QPoint(0, height()))); if (a) { const StatusSearchLine::Status newStatus = a->data().value(); updateStatusIcon(newStatus); } } void StatusSearchLine::updateStatusIcon(StatusSearchLine::Status status) { if (mDefaultStatus != status) { mDefaultStatus = status; mSearchLineStatusAction->setIcon(mHashStatus[status].mIcon); mSearchLineStatusAction->setToolTip(mHashStatus[status].mText); Q_EMIT statusChanged(mDefaultStatus); } } StatusSearchLine::Status StatusSearchLine::status() const { return mDefaultStatus; } diff --git a/src/widgets/statussearchline.h b/src/widgets/statussearchline.h index bcac6c2b..2d75f55d 100644 --- a/src/widgets/statussearchline.h +++ b/src/widgets/statussearchline.h @@ -1,76 +1,80 @@ /* Copyright (C) 2016-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. */ #ifndef STATUSSEARCHLINE_H #define STATUSSEARCHLINE_H #include #include #include class QAction; namespace Akregator { class StatusSearchLine : public KLineEdit { Q_OBJECT public: enum Status { AllArticles = 0, NewArticles, UnreadArticles, ImportantArticles }; explicit StatusSearchLine(QWidget *parent = nullptr); ~StatusSearchLine(); void setStatus(StatusSearchLine::Status status); Status status() const; Q_SIGNALS: void statusChanged(Akregator::StatusSearchLine::Status status); + void forceLostFocus(); + +protected: + void keyPressEvent(QKeyEvent *e) override; private Q_SLOTS: void showMenu(); private: struct StatusInfo { StatusInfo() { } StatusInfo(const QString &text, const QIcon &icon) : mText(text) , mIcon(icon) { } QString mText; QIcon mIcon; }; void initializeHash(); void initializeActions(); void updateStatusIcon(StatusSearchLine::Status status); Status mDefaultStatus; QHash mHashStatus; QAction *mSearchLineStatusAction = nullptr; }; } #endif // STATUSSEARCHLINE_H