diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dd042ee..c8cdc60d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,100 +1,100 @@ cmake_minimum_required(VERSION 3.1) set(KDEPIM_VERSION_NUMBER "5.8.40") project(akregator VERSION ${KDEPIM_VERSION_NUMBER}) set(KF5_VERSION "5.44.0") find_package(ECM ${KF5_VERSION} CONFIG REQUIRED) 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 alpha) # add an extra space if(DEFINED KDEPIM_DEV_VERSION) set(KDEPIM_DEV_VERSION " ${KDEPIM_DEV_VERSION}") endif() 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.9.0") -set(KONTACTINTERFACE_LIB_VERSION "5.7.80") -set(KPIMTEXTEDIT_LIB_VERSION "5.7.80") -set(LIBGRANTLEETHEME_LIB_VERSION_LIB "5.7.80") -set(LIBKDEPIM_LIB_VERSION_LIB "5.7.80") -set(LIBKLEO_LIB_VERSION_LIB "5.7.80") -set(MESSAGELIB_LIB_VERSION_LIB "5.7.80") -set(PIMCOMMON_LIB_VERSION_LIB "5.7.80") -set(SYNDICATION_LIB_VERSION "5.7.80") +set(KONTACTINTERFACE_LIB_VERSION "5.8.1") +set(KPIMTEXTEDIT_LIB_VERSION "5.8.1") +set(LIBGRANTLEETHEME_LIB_VERSION_LIB "5.8.1") +set(LIBKDEPIM_LIB_VERSION_LIB "5.8.1") +set(LIBKLEO_LIB_VERSION_LIB "5.8.1") +set(MESSAGELIB_LIB_VERSION_LIB "5.8.1") +set(PIMCOMMON_LIB_VERSION_LIB "5.8.1") +set(SYNDICATION_LIB_VERSION "5.8.1") 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) include_directories(${akregator_SOURCE_DIR} ${akregator_BINARY_DIR}) add_definitions(-DTRANSLATION_DOMAIN=\"akregator\") add_definitions(-DQT_NO_URL_CAST_FROM_STRING) add_definitions(-DQT_USE_QSTRINGBUILDER) 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) diff --git a/configuration/settings_advanced.cpp b/configuration/settings_advanced.cpp index 38c9fa68..2ca972d2 100644 --- a/configuration/settings_advanced.cpp +++ b/configuration/settings_advanced.cpp @@ -1,95 +1,95 @@ /* This file is part of Akregator. Copyright (C) 2005-2007 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 "settings_advanced.h" #include "akregatorconfig.h" #include "storagefactory.h" #include "storagefactoryregistry.h" #include #include #include #include #include using namespace Akregator; SettingsAdvanced::SettingsAdvanced(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(QLatin1String(name)); setupUi(this); const QStringList backends = Backend::StorageFactoryRegistry::self()->list(); for (const QString &i : backends) { Backend::StorageFactory *const factory = Backend::StorageFactoryRegistry::self()->getFactory(i); if (!factory) { continue; } m_factories.insert(factory->key(), factory); cbBackend->addItem(factory->name(), factory->key()); } connect(pbBackendConfigure, &QPushButton::clicked, this, &SettingsAdvanced::slotConfigureStorage); - connect(cbBackend, static_cast(&KComboBox::activated), this, &SettingsAdvanced::slotFactorySelected); + connect(cbBackend, QOverload::of(&KComboBox::activated), this, &SettingsAdvanced::slotFactorySelected); connect(kcfg_UseMarkReadDelay, &QCheckBox::toggled, kcfg_MarkReadDelay, &KPluralHandlingSpinBox::setEnabled); kcfg_MarkReadDelay->setSuffix(ki18ncp("Mark selected article read after", " second", " seconds")); } QString SettingsAdvanced::selectedFactory() const { return cbBackend->itemData(cbBackend->currentIndex()).toString(); } void SettingsAdvanced::selectFactory(const QString &key) { const int idx = cbBackend->findData(key); if (idx < 0) { return; } cbBackend->setCurrentIndex(idx); const Backend::StorageFactory *const factory = m_factories.value(key); Q_ASSERT(factory); pbBackendConfigure->setEnabled(factory->isConfigurable()); } void SettingsAdvanced::slotConfigureStorage() { const QString key = cbBackend->itemData(cbBackend->currentIndex()).toString(); if (!key.isEmpty()) { Backend::StorageFactory *const factory = m_factories.value(key); Q_ASSERT(factory); factory->configure(); } } void SettingsAdvanced::slotFactorySelected(int pos) { const QString key = cbBackend->itemData(pos).toString(); const Backend::StorageFactory *const factory = m_factories.value(key); Q_ASSERT(factory); pbBackendConfigure->setEnabled(factory->isConfigurable()); } diff --git a/src/articlelistview.cpp b/src/articlelistview.cpp index d90b632d..785a88b8 100644 --- a/src/articlelistview.cpp +++ b/src/articlelistview.cpp @@ -1,538 +1,535 @@ /* This file is part of Akregator. Copyright (C) 2004 Stanislav Karchebny 2005-2008 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 "articlelistview.h" #include "actionmanager.h" #include "akregatorconfig.h" #include "article.h" #include "articlemodel.h" #include "kernel.h" #include "types.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Akregator; FilterDeletedProxyModel::FilterDeletedProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { setDynamicSortFilter(true); } bool FilterDeletedProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { return !sourceModel()->index(source_row, 0, source_parent).data(ArticleModel::IsDeletedRole).toBool(); } SortColorizeProxyModel::SortColorizeProxyModel(QObject *parent) : QSortFilterProxyModel(parent) , m_keepFlagIcon(QIcon::fromTheme(QStringLiteral("mail-mark-important"))) { m_unreadColor = KColorScheme(QPalette::Normal, KColorScheme::View).foreground(KColorScheme::PositiveText).color(); m_newColor = KColorScheme(QPalette::Normal, KColorScheme::View).foreground(KColorScheme::NegativeText).color(); } bool SortColorizeProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (source_parent.isValid()) { return false; } for (uint i = 0; i < m_matchers.size(); ++i) { if (!static_cast(sourceModel())->rowMatches(source_row, m_matchers[i])) { return false; } } return true; } void SortColorizeProxyModel::setFilters(const std::vector > &matchers) { if (m_matchers == matchers) { return; } m_matchers = matchers; invalidateFilter(); } QVariant SortColorizeProxyModel::data(const QModelIndex &idx, int role) const { if (!idx.isValid() || !sourceModel()) { return QVariant(); } const QModelIndex sourceIdx = mapToSource(idx); switch (role) { case Qt::ForegroundRole: switch (static_cast(sourceIdx.data(ArticleModel::StatusRole).toInt())) { case Unread: return Settings::useCustomColors() ? Settings::colorUnreadArticles() : m_unreadColor; case New: return Settings::useCustomColors() ? Settings::colorNewArticles() : m_newColor; case Read: return QApplication::palette().color(QPalette::Text); } break; case Qt::DecorationRole: if (sourceIdx.column() == ArticleModel::ItemTitleColumn) { return sourceIdx.data(ArticleModel::IsImportantRole).toBool() ? m_keepFlagIcon : QVariant(); } break; } return sourceIdx.data(role); } namespace { static bool isRead(const QModelIndex &idx) { if (!idx.isValid()) { return false; } return static_cast(idx.data(ArticleModel::StatusRole).toInt()) == Read; } } void ArticleListView::setArticleModel(ArticleModel *model) { if (!model) { setModel(model); return; } m_proxy = new SortColorizeProxyModel(model); m_proxy->setSourceModel(model); m_proxy->setSortRole(ArticleModel::SortRole); m_proxy->setFilters(m_matchers); FilterDeletedProxyModel *const proxy2 = new FilterDeletedProxyModel(model); proxy2->setSortRole(ArticleModel::SortRole); proxy2->setSourceModel(m_proxy); connect(model, &QAbstractItemModel::rowsInserted, m_proxy.data(), &QSortFilterProxyModel::invalidate); FilterColumnsProxyModel *const columnsProxy = new FilterColumnsProxyModel(model); columnsProxy->setSortRole(ArticleModel::SortRole); columnsProxy->setSourceModel(proxy2); columnsProxy->setColumnEnabled(ArticleModel::ItemTitleColumn); columnsProxy->setColumnEnabled(ArticleModel::FeedTitleColumn); columnsProxy->setColumnEnabled(ArticleModel::DateColumn); columnsProxy->setColumnEnabled(ArticleModel::AuthorColumn); setModel(columnsProxy); header()->setContextMenuPolicy(Qt::CustomContextMenu); header()->setSectionResizeMode(QHeaderView::Interactive); } void ArticleListView::showHeaderMenu(const QPoint &pos) { if (!model()) { return; } QPointer menu = new QMenu(this); menu->setTitle(i18n("Columns")); menu->setAttribute(Qt::WA_DeleteOnClose); const int colCount = model()->columnCount(); int visibleColumns = 0; // number of column currently shown QAction *visibleColumnsAction = nullptr; for (int i = 0; i < colCount; ++i) { QAction *act = menu->addAction(model()->headerData(i, Qt::Horizontal).toString()); act->setCheckable(true); act->setData(i); bool sectionVisible = !header()->isSectionHidden(i); act->setChecked(sectionVisible); if (sectionVisible) { ++visibleColumns; visibleColumnsAction = act; } } // Avoid that the last shown column is also hidden if (visibleColumns == 1) { visibleColumnsAction->setEnabled(false); } QPointer that(this); QAction *const action = menu->exec(header()->mapToGlobal(pos)); if (that && action) { const int col = action->data().toInt(); if (action->isChecked()) { header()->showSection(col); } else { header()->hideSection(col); } } delete menu; } void ArticleListView::saveHeaderSettings() { if (model()) { const QByteArray state = header()->saveState(); if (m_columnMode == FeedMode) { m_feedHeaderState = state; } else { m_groupHeaderState = state; } } KConfigGroup conf(Settings::self()->config(), "General"); conf.writeEntry("ArticleListFeedHeaders", m_feedHeaderState.toBase64()); conf.writeEntry("ArticleListGroupHeaders", m_groupHeaderState.toBase64()); } void ArticleListView::loadHeaderSettings() { KConfigGroup conf(Settings::self()->config(), "General"); m_feedHeaderState = QByteArray::fromBase64(conf.readEntry("ArticleListFeedHeaders").toLatin1()); m_groupHeaderState = QByteArray::fromBase64(conf.readEntry("ArticleListGroupHeaders").toLatin1()); } QItemSelectionModel *ArticleListView::articleSelectionModel() const { return selectionModel(); } const QAbstractItemView *ArticleListView::itemView() const { return this; } QAbstractItemView *ArticleListView::itemView() { return this; } QPoint ArticleListView::scrollBarPositions() const { return QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value()); } void ArticleListView::setScrollBarPositions(const QPoint &p) { horizontalScrollBar()->setValue(p.x()); verticalScrollBar()->setValue(p.y()); } void ArticleListView::setGroupMode() { if (m_columnMode == GroupMode) { return; } if (model()) { m_feedHeaderState = header()->saveState(); } m_columnMode = GroupMode; restoreHeaderState(); } void ArticleListView::setFeedMode() { if (m_columnMode == FeedMode) { return; } if (model()) { m_groupHeaderState = header()->saveState(); } m_columnMode = FeedMode; restoreHeaderState(); } static int maxDateColumnWidth(const QFontMetrics &fm) { int width = 0; QDateTime date(QDate::currentDate(), QTime(23, 59)); for (int x = 0; x < 10; ++x, date = date.addDays(-1)) { QString txt = QLatin1Char(' ') + QLocale().toString(date, QLocale::ShortFormat) + QLatin1Char(' '); width = qMax(width, fm.width(txt)); } return width; } void ArticleListView::restoreHeaderState() { QByteArray state = m_columnMode == GroupMode ? m_groupHeaderState : m_feedHeaderState; header()->restoreState(state); if (state.isEmpty()) { // No state, set a default config: // - hide the feed column in feed mode (no need to see the same feed title over and over) // - set the date column wide enough to fit all possible dates header()->setSectionHidden(ArticleModel::FeedTitleColumn, m_columnMode == FeedMode); header()->setStretchLastSection(false); header()->resizeSection(ArticleModel::DateColumn, maxDateColumnWidth(fontMetrics())); if (model()) { startResizingTitleColumn(); } } if (header()->sectionSize(ArticleModel::DateColumn) == 1) { header()->resizeSection(ArticleModel::DateColumn, maxDateColumnWidth(fontMetrics())); } } void ArticleListView::startResizingTitleColumn() { // set the title column to Stretch resize mode so that it adapts to the // content. finishResizingTitleColumn() will turn the resize mode back to // Interactive so that the user can still resize the column himself if he // wants to header()->setSectionResizeMode(ArticleModel::ItemTitleColumn, QHeaderView::Stretch); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(this, &ArticleListView::finishResizingTitleColumn, Qt::QueuedConnection); #else QMetaObject::invokeMethod(this, "finishResizingTitleColumn", Qt::QueuedConnection); #endif } void ArticleListView::finishResizingTitleColumn() { if (QApplication::mouseButtons() != Qt::NoButton) { // Come back later: user is still resizing the widget #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(this, &ArticleListView::finishResizingTitleColumn, Qt::QueuedConnection); #else QMetaObject::invokeMethod(this, "finishResizingTitleColumn", Qt::QueuedConnection); #endif return; } header()->setSectionResizeMode(QHeaderView::Interactive); } ArticleListView::~ArticleListView() { saveHeaderSettings(); } void ArticleListView::setIsAggregation(bool aggregation) { if (aggregation) { setGroupMode(); } else { setFeedMode(); } } ArticleListView::ArticleListView(QWidget *parent) : QTreeView(parent) , m_columnMode(FeedMode) { setSortingEnabled(true); setAlternatingRowColors(true); setSelectionMode(QAbstractItemView::ExtendedSelection); setUniformRowHeights(true); setRootIsDecorated(false); setAllColumnsShowFocus(true); setDragDropMode(QAbstractItemView::DragOnly); setMinimumSize(250, 150); setWhatsThis(i18n("

Article list

" "Here you can browse articles from the currently selected feed. " "You can also manage articles, as marking them as persistent (\"Mark as Important\") or delete them, using the right mouse button menu. " "To view the web page of the article, you can open the article internally in a tab or in an external browser window.")); //connect exactly once disconnect(header(), &QWidget::customContextMenuRequested, this, &ArticleListView::showHeaderMenu); connect(header(), &QWidget::customContextMenuRequested, this, &ArticleListView::showHeaderMenu); loadHeaderSettings(); } void ArticleListView::mousePressEvent(QMouseEvent *ev) { // let's push the event, so we can use currentIndex() to get the newly selected article.. QTreeView::mousePressEvent(ev); if (ev->button() == Qt::MidButton) { const QUrl url = currentIndex().data(ArticleModel::LinkRole).toUrl(); Q_EMIT signalMouseButtonPressed(ev->button(), url); } } void ArticleListView::contextMenuEvent(QContextMenuEvent *event) { QWidget *w = ActionManager::getInstance()->container(QStringLiteral("article_popup")); QMenu *popup = qobject_cast(w); if (popup) { popup->exec(event->globalPos()); } } void ArticleListView::setModel(QAbstractItemModel *m) { const bool groupMode = m_columnMode == GroupMode; QAbstractItemModel *const oldModel = model(); if (oldModel) { const QByteArray state = header()->saveState(); if (groupMode) { m_groupHeaderState = state; } else { m_feedHeaderState = state; } } QTreeView::setModel(m); if (m) { sortByColumn(ArticleModel::DateColumn, Qt::DescendingOrder); restoreHeaderState(); // Ensure at least one column is visible if (header()->hiddenSectionCount() == header()->count()) { header()->showSection(ArticleModel::ItemTitleColumn); } } } void ArticleListView::slotClear() { setModel(nullptr); } void ArticleListView::slotPreviousArticle() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); const QModelIndex idx = currentIndex(); const int newRow = qMax(0, (idx.isValid() ? idx.row() : model()->rowCount()) - 1); const QModelIndex newIdx = idx.isValid() ? idx.sibling(newRow, 0) : model()->index(newRow, 0); selectIndex(newIdx); } void ArticleListView::slotNextArticle() { if (!model()) { return; } Q_EMIT userActionTakingPlace(); const QModelIndex idx = currentIndex(); const int newRow = idx.isValid() ? (idx.row() + 1) : 0; const QModelIndex newIdx = model()->index(qMin(newRow, model()->rowCount() - 1), 0); selectIndex(newIdx); } void ArticleListView::slotNextUnreadArticle() { if (!model()) { return; } const int rowCount = model()->rowCount(); const int startRow = qMin(rowCount - 1, (currentIndex().isValid() ? currentIndex().row() + 1 : 0)); int i = startRow; bool foundUnread = false; do { if (!::isRead(model()->index(i, 0))) { foundUnread = true; } else { i = (i + 1) % rowCount; } } while (!foundUnread && i != startRow); if (foundUnread) { selectIndex(model()->index(i, 0)); } } void ArticleListView::selectIndex(const QModelIndex &idx) { if (!idx.isValid()) { return; } setCurrentIndex(idx); - clearSelection(); - Q_ASSERT(selectionModel()); - selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); scrollTo(idx, PositionAtCenter); } void ArticleListView::slotPreviousUnreadArticle() { if (!model()) { return; } const int rowCount = model()->rowCount(); const int startRow = qMax(0, (currentIndex().isValid() ? currentIndex().row() : rowCount) - 1); int i = startRow; bool foundUnread = false; do { if (!::isRead(model()->index(i, 0))) { foundUnread = true; } else { i = i > 0 ? i - 1 : rowCount - 1; } } while (!foundUnread && i != startRow); if (foundUnread) { selectIndex(model()->index(i, 0)); } } void ArticleListView::forceFilterUpdate() { if (m_proxy) { m_proxy->invalidate(); } } void ArticleListView::setFilters(const std::vector > &matchers) { if (m_matchers == matchers) { return; } m_matchers = matchers; if (m_proxy) { m_proxy->setFilters(matchers); } }