diff --git a/CMakeLists.txt b/CMakeLists.txt index b019fad6..f7ee7ca8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,100 +1,99 @@ cmake_minimum_required(VERSION 3.5) set(KDEPIM_VERSION_NUMBER "5.13.40") project(akregator VERSION ${KDEPIM_VERSION_NUMBER}) set(KF5_MIN_VERSION "5.64.0") set(RELEASE_SERVICE_VERSION "20.03.70") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMAddTests) include(GenerateExportHeader) include(ECMGenerateHeaders) include(FeatureSummary) include(CheckFunctionExists) include(ECMGeneratePriFile) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMAddAppIcon) include(ECMQtDeclareLoggingCategory) # 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} (${RELEASE_SERVICE_VERSION})") set(KDEPIM_LIB_VERSION "${KDEPIM_VERSION_NUMBER}") set(KDEPIM_LIB_SOVERSION "5") set(QT_REQUIRED_VERSION "5.12.0") set(KONTACTINTERFACE_LIB_VERSION "5.13.40") set(KPIMTEXTEDIT_LIB_VERSION "5.13.40") set(LIBGRANTLEETHEME_LIB_VERSION_LIB "5.13.40") set(LIBKDEPIM_LIB_VERSION_LIB "5.13.40") set(LIBKLEO_LIB_VERSION_LIB "5.13.40") set(MESSAGELIB_LIB_VERSION_LIB "5.13.40") set(PIMCOMMON_LIB_VERSION_LIB "5.13.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_MIN_VERSION} REQUIRED) find_package(KF5DocTools ${KF5_MIN_VERSION} REQUIRED) find_package(KF5KCMUtils ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5NotifyConfig ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Parts ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5TextEditor ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_MIN_VERSION} CONFIG REQUIRED) -find_package(KF5IconThemes ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Notifications ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Syndication ${KF5_MIN_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(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\") set(CMAKE_CXX_STANDARD 14) add_definitions(-DQT_NO_FOREACH) if (EXISTS "${CMAKE_SOURCE_DIR}/.git") add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x060000) endif() 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_LOGGINGCATEGORIESDIR}) add_subdirectory(doc) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/configuration/CMakeLists.txt b/configuration/CMakeLists.txt index 8473e4f9..4d4565f9 100644 --- a/configuration/CMakeLists.txt +++ b/configuration/CMakeLists.txt @@ -1,121 +1,120 @@ include_directories( ${akregator_BINARY_DIR}/interfaces ${akregator_SOURCE_DIR}/interfaces ) ########### next target ############### set(kcm_akrgeneralconfig_SRCS akregator_config_general.cpp) ki18n_wrap_ui(kcm_akrgeneralconfig_SRCS ui/settings_general.ui) add_library(akregator_config_general MODULE ${kcm_akrgeneralconfig_SRCS}) target_link_libraries(akregator_config_general akregatorinterfaces KF5::TextWidgets KF5::KCMUtils KF5::I18n ) install(FILES akregator_config_general.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) kcoreaddons_desktop_to_json(akregator_config_general akregator_config_general.desktop SERVICE_TYPES kcmodule.desktop) install(TARGETS akregator_config_general DESTINATION ${KDE_INSTALL_PLUGINDIR}) ########### next target ############### set(kcm_akrappearanceconfig_SRCS akregator_config_appearance.cpp) ki18n_wrap_ui(kcm_akrappearanceconfig_SRCS ui/settings_appearance.ui) add_library(akregator_config_appearance MODULE ${kcm_akrappearanceconfig_SRCS}) target_link_libraries(akregator_config_appearance akregatorinterfaces KF5::TextWidgets KF5::KCMUtils KF5::I18n ) kcoreaddons_desktop_to_json(akregator_config_appearance akregator_config_appearance.desktop SERVICE_TYPES kcmodule.desktop) install(FILES akregator_config_appearance.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(TARGETS akregator_config_appearance DESTINATION ${KDE_INSTALL_PLUGINDIR}) ########### next target ############### set(kcm_akrarchiveconfig_SRCS akregator_config_archive.cpp) ki18n_wrap_ui(kcm_akrarchiveconfig_SRCS ui/settings_archive.ui) add_library(akregator_config_archive MODULE ${kcm_akrarchiveconfig_SRCS}) target_link_libraries(akregator_config_archive KF5::KCMUtils akregatorinterfaces KF5::TextWidgets KF5::I18n ) #kcoreaddons_desktop_to_json(akregator_config_archive akregator_config_archive.desktop DEFAULT_SERVICE_TYPE) install(FILES akregator_config_archive.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(TARGETS akregator_config_archive DESTINATION ${KDE_INSTALL_PLUGINDIR}) ########### next target ############### set(kcm_akrbrowserconfig_SRCS akregator_config_browser.cpp) ki18n_wrap_ui(kcm_akrbrowserconfig_SRCS ui/settings_browser.ui) add_library(akregator_config_browser MODULE ${kcm_akrbrowserconfig_SRCS}) target_link_libraries(akregator_config_browser akregatorinterfaces KF5::KCMUtils KF5::I18n ) kcoreaddons_desktop_to_json(akregator_config_browser akregator_config_browser.desktop SERVICE_TYPES kcmodule.desktop) install(FILES akregator_config_browser.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(TARGETS akregator_config_browser DESTINATION ${KDE_INSTALL_PLUGINDIR}) ########### next target ############### set(kcm_akradvancedconfig_SRCS akregator_config_advanced.cpp settings_advanced.cpp ) ki18n_wrap_ui(kcm_akradvancedconfig_SRCS ui/settings_advancedbase.ui) add_library(akregator_config_advanced MODULE ${kcm_akradvancedconfig_SRCS}) target_link_libraries(akregator_config_advanced akregatorinterfaces KF5::TextWidgets KF5::KCMUtils KF5::I18n ) kcoreaddons_desktop_to_json(akregator_config_advanced akregator_config_advanced.desktop SERVICE_TYPES kcmodule.desktop) install(FILES akregator_config_advanced.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(TARGETS akregator_config_advanced DESTINATION ${KDE_INSTALL_PLUGINDIR}) ########### next target ############### set(kcm_config_plugins_SRCS akregator_config_plugins.cpp akregatorconfigurepluginlistwidget.cpp ) ecm_qt_declare_logging_category(kcm_config_plugins_SRCS HEADER kcm_config_plugins_debug.h IDENTIFIER AKREGATOR_CONFIG_PLUGIN_LOG CATEGORY_NAME org.kde.pim.akregator_config_plugin) add_library(akregator_config_plugins MODULE ${kcm_config_plugins_SRCS}) target_link_libraries(akregator_config_plugins akregatorinterfaces KF5::KCMUtils KF5::I18n - KF5::IconThemes KF5::WebEngineViewer ) kcoreaddons_desktop_to_json(akregator_config_plugins akregator_config_plugins.desktop SERVICE_TYPES kcmodule.desktop) install(FILES akregator_config_plugins.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(TARGETS akregator_config_plugins DESTINATION ${KDE_INSTALL_PLUGINDIR}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e6e96f15..b687923f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,238 +1,236 @@ configure_file(akregator-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/akregator-version.h @ONLY) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/command/ ${CMAKE_CURRENT_SOURCE_DIR}/formatter/ ${CMAKE_CURRENT_SOURCE_DIR}/subscription/ ${CMAKE_CURRENT_SOURCE_DIR}/feed/ ${CMAKE_CURRENT_SOURCE_DIR}/urlhandler/ ${CMAKE_CURRENT_SOURCE_DIR}/actions/ ${CMAKE_CURRENT_SOURCE_DIR}/frame/ ) add_subdirectory(icons) ########### next target ############### set(akregator_common_SRCS) ecm_qt_declare_logging_category(akregator_common_SRCS HEADER akregator_debug.h IDENTIFIER AKREGATOR_LOG CATEGORY_NAME org.kde.pim.akregator) set(akregator_SRCS main.cpp mainwindow.cpp ${akregator_common_SRCS}) file(GLOB ICONS_AKREGATOR_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-akregator.png") ecm_add_app_icon(akregator_SRCS ICONS ${ICONS_AKREGATOR_SRCS}) add_executable(akregator ${akregator_SRCS}) target_link_libraries(akregator KF5::Crash KF5::Notifications KF5::KontactInterface KF5::Libkdepim akregatorprivate akregatorinterfaces KF5::PimCommon ) install(TARGETS akregator ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########### next target ############### set(akregator_articleviewer_ng_webengine_SRCS articleviewer-ng/webengine/articleviewerwebengine.cpp articleviewer-ng/webengine/articleviewerwebenginepage.cpp articleviewer-ng/webengine/articlehtmlwebenginewriter.cpp articleviewer-ng/webengine/articleviewerwebenginewidgetng.cpp ) set(akregator_job_SRCS job/downloadarticlejob.cpp job/downloadfeediconjob.cpp ) set(akregatorprivate_formatter_SRCS formatter/articleformatter.cpp formatter/defaultnormalviewformatter.cpp formatter/defaultcombinedviewformatter.cpp formatter/grantleeviewformatter.cpp formatter/articlegrantleeobject.cpp formatter/grantleeutil.cpp ) set(akregatorprivate_frame_SRCS frame/webengine/webengineframe.cpp frame/webengine/akrwebengineviewer.cpp frame/frame.cpp frame/mainframe.cpp frame/framemanager.cpp ) set(akregatorprivate_urlhandlerwebengine_SRCS urlhandler/webengine/urlhandlerwebengine.cpp urlhandler/webengine/urlhandlerwebenginemanager.cpp ) set(akregatorprivate_LIB_SRCS akregratormigrateapplication.cpp ${akregatorprivate_frame_SRCS} ${akregatorprivate_urlhandler_SRCS} ${akregator_articleviewer_ng_SRCS} ${akregator_articleviewer_ng_webengine_SRCS} ${akregator_common_SRCS} ${akregatorprivate_formatter_SRCS} ${akregatorprivate_urlhandlerwebengine_SRCS} ${akregator_job_SRCS} articleviewerwidget.cpp aboutdata.cpp trayicon.cpp unityservicemanager.cpp article.cpp feed/feed.cpp feed/feedlist.cpp feed/feedretriever.cpp treenode.cpp treenodevisitor.cpp utils.cpp notificationmanager.cpp articlejobs.cpp folder.cpp kernel.cpp subscription/subscriptionlistjobs.cpp fetchqueue.cpp openurlrequest.cpp actions/actionmanager.cpp actions/actions.cpp ) qt5_add_resources(akregatorprivate_LIB_SRCS akregator.qrc) add_library(akregatorprivate ${akregatorprivate_LIB_SRCS}) generate_export_header(akregatorprivate BASE_NAME akregator) target_link_libraries(akregatorprivate PRIVATE KF5::Parts KF5::Notifications KF5::Libkdepim KF5::Syndication akregatorinterfaces KF5::PimCommon - KF5::IconThemes KF5::PimTextEdit KF5::GrantleeTheme Grantlee5::Templates KF5::KIOGui KF5::MessageViewer Qt5::PrintSupport KF5::WebEngineViewer ) target_include_directories(akregatorprivate PUBLIC "$") set_target_properties(akregatorprivate PROPERTIES VERSION ${KDEPIM_LIB_VERSION} SOVERSION ${KDEPIM_LIB_SOVERSION} ) install(TARGETS akregatorprivate ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) ########### next target ############### set(akregator_crashwidget_SRCS crashwidget/crashwidget.cpp ) set(akregator_utils_SRCS utils/filtercolumnsproxymodel.cpp ) set(akregatorpart_command_SRCS command/deletesubscriptioncommand.cpp command/createfeedcommand.cpp command/createfoldercommand.cpp command/expireitemscommand.cpp command/loadfeedlistcommand.cpp command/editsubscriptioncommand.cpp command/importfeedlistcommand.cpp ) set(akregatorpart_widgets_SRCS widgets/statussearchline.cpp widgets/searchbar.cpp widgets/akregatorcentralwidget.cpp ) set(akregatorpart_subscription_SRCS subscription/subscriptionlistview.cpp subscription/subscriptionlistdelegate.cpp subscription/subscriptionlistmodel.cpp ) set(akregatorpart_PART_SRCS ${akregator_crashwidget_SRCS} ${akregatorpart_subscription_SRCS} ${akregatorpart_widgets_SRCS} ${akregatorpart_command_SRCS} ${akregator_utils_SRCS} ${akregator_common_SRCS} abstractselectioncontroller.cpp articlematcher.cpp articlemodel.cpp pluginmanager.cpp selectioncontroller.cpp articlelistview.cpp actions/actionmanagerimpl.cpp addfeeddialog.cpp feed/feedpropertiesdialog.cpp tabwidget.cpp progressmanager.cpp akregator_part.cpp mainwidget.cpp dummystorage/storagedummyimpl.cpp dummystorage/storagefactorydummyimpl.cpp dummystorage/feedstoragedummyimpl.cpp ) qt5_add_dbus_adaptor(akregatorpart_PART_SRCS org.kde.akregator.part.xml akregator_part.h Akregator::Part) ki18n_wrap_ui(akregatorpart_PART_SRCS ui/addfeedwidgetbase.ui ui/feedpropertieswidgetbase.ui ) add_library(akregatorpart MODULE ${akregatorpart_PART_SRCS}) generate_export_header(akregatorpart BASE_NAME akregatorpart) target_link_libraries(akregatorpart akregatorinterfaces akregatorprivate KF5::KCMUtils KF5::NotifyConfig KF5::Libkdepim KF5::Syndication KF5::PimCommon KF5::PimTextEdit KF5::GrantleeTheme KF5::MessageViewer - KF5::IconThemes KF5::Parts KF5::Notifications Qt5::WebEngineWidgets KF5::WebEngineViewer KF5::KIOGui ) install(TARGETS akregatorpart DESTINATION ${KDE_INSTALL_PLUGINDIR}) ########### install files ############### install(PROGRAMS data/org.kde.akregator.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES data/akregator_part.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(FILES data/org.kde.akregator.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES data/akregator_plugin.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) install(FILES feed.protocol DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) install(FILES org.kde.akregator.part.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) install(FILES data/akregator.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR} ) add_subdirectory(formatter/html) if(BUILD_TESTING) add_subdirectory(job/autotests) endif() diff --git a/src/addfeeddialog.cpp b/src/addfeeddialog.cpp index 18920894..0262f076 100644 --- a/src/addfeeddialog.cpp +++ b/src/addfeeddialog.cpp @@ -1,150 +1,149 @@ /* This file is part of Akregator. Copyright (C) 2004 Stanislav Karchebny This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "addfeeddialog.h" #include "feed.h" #include "kernel.h" #include "akregator_debug.h" -#include -#include #include #include #include #include #include #include +#include using namespace Akregator; AddFeedWidget::AddFeedWidget(QWidget *parent) : QWidget(parent) { setupUi(this); - pixmapLabel1->setPixmap(QIcon::fromTheme(QStringLiteral("applications-internet")).pixmap(IconSize(KIconLoader::Desktop), IconSize(KIconLoader::Desktop))); + pixmapLabel1->setPixmap(QIcon::fromTheme(QStringLiteral("applications-internet")).pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize))); statusLabel->setText(QString()); } AddFeedWidget::~AddFeedWidget() { } QSize AddFeedDialog::sizeHint() const { QSize sh = QDialog::sizeHint(); sh.setHeight(minimumSize().height()); sh.setWidth(sh.width() * 1.2); return sh; } Feed *AddFeedDialog::feed() const { return m_feed; } AddFeedDialog::AddFeedDialog(QWidget *parent, const QString &name) : QDialog(parent) { setObjectName(name); setWindowTitle(i18n("Add Feed")); QVBoxLayout *mainLayout = new QVBoxLayout(this); widget = new AddFeedWidget(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); mOkButton = buttonBox->button(QDialogButtonBox::Ok); mOkButton->setDefault(true); mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &AddFeedDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &AddFeedDialog::reject); mainLayout->addWidget(widget); mainLayout->addWidget(buttonBox); widget->urlEdit->setFocus(); connect(widget->urlEdit, &QLineEdit::textChanged, this, &AddFeedDialog::textChanged); mOkButton->setEnabled(false); } AddFeedDialog::~AddFeedDialog() { } void AddFeedDialog::setUrl(const QString &t) { widget->urlEdit->setText(t); } void AddFeedDialog::accept() { mOkButton->setEnabled(false); mFeedUrl = widget->urlEdit->text().trimmed(); delete m_feed; m_feed = new Feed(Kernel::self()->storage()); // HACK: make weird wordpress links ("feed:http://foobar/rss") work if (mFeedUrl.startsWith(QLatin1String("feed:http"))) { mFeedUrl = mFeedUrl.right(mFeedUrl.length() - 5); } if (!mFeedUrl.contains(QLatin1String(":/"))) { mFeedUrl.prepend(QLatin1String("https://")); } QUrl asUrl(mFeedUrl); if (asUrl.scheme() == QLatin1String("feed")) { asUrl.setScheme(QStringLiteral("https")); mFeedUrl = asUrl.url(); } m_feed->setXmlUrl(mFeedUrl); widget->statusLabel->setText(i18n("Downloading %1", mFeedUrl)); connect(m_feed, &Feed::fetched, this, &AddFeedDialog::fetchCompleted); connect(m_feed, &Feed::fetchError, this, &AddFeedDialog::fetchError); connect(m_feed, &Feed::fetchDiscovery, this, &AddFeedDialog::fetchDiscovery); m_feed->fetch(true); } void AddFeedDialog::fetchCompleted(Feed * /*f*/) { QDialog::accept(); } void AddFeedDialog::fetchError(Feed *) { KMessageBox::error(this, i18n("Feed not found from %1.", mFeedUrl)); QDialog::reject(); } void AddFeedDialog::fetchDiscovery(Feed *f) { widget->statusLabel->setText(i18n("Feed found, downloading...")); mFeedUrl = f->xmlUrl(); } void AddFeedDialog::textChanged(const QString &text) { mOkButton->setEnabled(!text.trimmed().isEmpty()); } diff --git a/src/subscription/subscriptionlistdelegate.cpp b/src/subscription/subscriptionlistdelegate.cpp index 5db36123..e2452f78 100644 --- a/src/subscription/subscriptionlistdelegate.cpp +++ b/src/subscription/subscriptionlistdelegate.cpp @@ -1,119 +1,119 @@ /* This file is part of Akregator. Copyright (C) 2007 Frank Osterfeld Copyright (C) 2009 Jonathan Marten 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 "subscriptionlistdelegate.h" #include "subscriptionlistmodel.h" #include "akregator_debug.h" -#include +#include #include #include +#include using namespace Akregator; Akregator::SubscriptionListDelegate::SubscriptionListDelegate(QWidget *parent) : QStyledItemDelegate(parent) { //TODO reimplement //connect(KGlobalSettings::self(), &KGlobalSettings::appearanceChanged, this, &SubscriptionListDelegate::recalculateRowHeight); recalculateRowHeight(); } Akregator::SubscriptionListDelegate::~SubscriptionListDelegate() { } QSize Akregator::SubscriptionListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size = QStyledItemDelegate::sizeHint(option, index); size.setHeight(qMax(size.height(), (m_viewIconHeight + 2))); // +2 for row top/bottom margin return size; } void Akregator::SubscriptionListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem newOption = option; if (index.data(SubscriptionListModel::HasUnreadRole).toBool()) { // feed has unread articles newOption.font.setBold(true); } //fix [Bug 190052] numeric columns aligned to the left if (index.column() == SubscriptionListModel::UnreadCountColumn || index.column() == SubscriptionListModel::TotalCountColumn) { newOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter; } else { newOption.displayAlignment = Qt::AlignLeft | Qt::AlignVCenter; } // No need to translate the painter here - the item is vertically centered // within its sizeHint rectangle. QStyledItemDelegate::paint(painter, newOption, index); } void Akregator::SubscriptionListDelegate::recalculateRowHeight() { - KIconTheme *iconTheme = KIconLoader::global()->theme(); - m_viewIconHeight = iconTheme ? iconTheme->defaultSize(KIconLoader::Small) : 0; + m_viewIconHeight = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize); qCDebug(AKREGATOR_LOG) << "icon height" << m_viewIconHeight; } void Akregator::SubscriptionListDelegate::initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const { QStyledItemDelegate::initStyleOption(option, index); if (index.column() != 0) { // Append unread count to the title column only (it is always the first // one) return; } QTreeView *view = static_cast< QTreeView * >(parent()); if (!view->header()->isSectionHidden(SubscriptionListModel::UnreadCountColumn)) { // Do not append unread count to the title if the unread count column // is visible return; } else { view->header()->resizeSection(SubscriptionListModel::UnreadCountColumn, QHeaderView::ResizeToContents); } if (!view->header()->isSectionHidden(SubscriptionListModel::TotalCountColumn)) { view->header()->resizeSection(SubscriptionListModel::TotalCountColumn, QHeaderView::ResizeToContents); } QStyleOptionViewItem *optionV4 = qstyleoption_cast< QStyleOptionViewItem * >(option); if (!optionV4) { // Should never happen, but play it safe return; } QModelIndex unreadIndex = index.sibling(index.row(), SubscriptionListModel::UnreadCountColumn); int unread = unreadIndex.data().toInt(); if (unread > 0) { optionV4->text += QStringLiteral(" (%1)").arg(unread); } } diff --git a/src/subscription/subscriptionlistmodel.cpp b/src/subscription/subscriptionlistmodel.cpp index 3dd3d467..46189658 100644 --- a/src/subscription/subscriptionlistmodel.cpp +++ b/src/subscription/subscriptionlistmodel.cpp @@ -1,577 +1,579 @@ /* This file is part of Akregator. Copyright (C) 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 "subscriptionlistmodel.h" #include "feed.h" #include "feedlist.h" #include "folder.h" #include "subscriptionlistjobs.h" #include "treenode.h" #include "akregator_debug.h" -#include #include +#include #include #include #include #include #include #include #include #include +#include using namespace Akregator; using namespace Syndication; #define AKREGATOR_TREENODE_MIMETYPE QStringLiteral("akregator/treenode-id") namespace { static uint nodeIdForIndex(const QModelIndex &idx) { return idx.isValid() ? idx.internalId() : 0; } static QString errorCodeToString(Syndication::ErrorCode err) { switch (err) { case Timeout: return i18n("Timeout on remote server"); case UnknownHost: return i18n("Unknown host"); case FileNotFound: return i18n("Feed file not found on remote server"); case InvalidXml: return i18n("Could not read feed (invalid XML)"); case XmlNotAccepted: return i18n("Could not read feed (unknown format)"); case InvalidFormat: return i18n("Could not read feed (invalid feed)"); case Success: case Aborted: default: return QString(); } } static const Akregator::TreeNode *nodeForIndex(const QModelIndex &index, const FeedList *feedList) { return (!index.isValid() || !feedList) ? nullptr : feedList->findByID(index.internalId()); } } Akregator::FilterUnreadProxyModel::FilterUnreadProxyModel(QObject *parent) : QSortFilterProxyModel(parent) , m_doFilter(false) , m_selectedHierarchy() { setDynamicSortFilter(true); } bool Akregator::FilterUnreadProxyModel::doFilter() const { return m_doFilter; } void Akregator::FilterUnreadProxyModel::setDoFilter(bool v) { m_doFilter = v; invalidateFilter(); } void Akregator::FilterUnreadProxyModel::setSourceModel(QAbstractItemModel *src) { clearCache(); QSortFilterProxyModel::setSourceModel(src); } bool Akregator::FilterUnreadProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (!m_doFilter) { return true; } QModelIndex idx = sourceModel()->index(source_row, 0, source_parent); if (m_selectedHierarchy.contains(idx)) { return true; } QVariant v = idx.data(SubscriptionListModel::HasUnreadRole); if (v.isNull()) { return true; } return v.toBool(); } /** * This caches the hierarchy of the selected node. Its purpose is to allow * feeds/folders with no unread content not to be filtered out immediately, * which would occur otherwise (we'd select the last article to read, it would * become unread, and disappear from the list without letting us view it). **/ void Akregator::FilterUnreadProxyModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndexList desel = mapSelectionToSource(deselected).indexes(); //calling invalidateFilter causes refiltering at the call point, so we should //call it ONLY after we recreate our node cache bool doInvalidate = false; //if we're deselecting an empty feed/folder, we need to hide it if (!desel.isEmpty()) { if (m_selectedHierarchy.contains(desel.at(0))) { doInvalidate = true; } } clearCache(); QModelIndexList sel = mapSelectionToSource(selected).indexes(); if (!sel.isEmpty()) { //XXX add support for multiple selections? this doesn't generally make sense in this case honestly QModelIndex current = sel.at(0); while (current.isValid()) { m_selectedHierarchy.insert(current); current = current.parent(); } } if (doInvalidate && doFilter()) { invalidateFilter(); } } void Akregator::FilterUnreadProxyModel::clearCache() { m_selectedHierarchy.clear(); } Akregator::SubscriptionListModel::SubscriptionListModel(const QSharedPointer &feedList, QObject *parent) : QAbstractItemModel(parent) , m_feedList(feedList) , m_beganRemoval(false) { if (!m_feedList) { return; } connect(m_feedList.data(), &FeedList::signalNodeAdded, this, &SubscriptionListModel::subscriptionAdded); connect(m_feedList.data(), &FeedList::signalAboutToRemoveNode, this, &SubscriptionListModel::aboutToRemoveSubscription); connect(m_feedList.data(), &FeedList::signalNodeRemoved, this, &SubscriptionListModel::subscriptionRemoved); connect(m_feedList.data(), &FeedList::signalNodeChanged, this, &SubscriptionListModel::subscriptionChanged); connect(m_feedList.data(), &FeedList::fetchStarted, this, &SubscriptionListModel::fetchStarted); connect(m_feedList.data(), &FeedList::fetched, this, &SubscriptionListModel::fetched); connect(m_feedList.data(), &FeedList::fetchAborted, this, &SubscriptionListModel::fetchAborted); } int Akregator::SubscriptionListModel::columnCount(const QModelIndex &) const { return 3; } int Akregator::SubscriptionListModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return 1; } const Akregator::TreeNode *const node = nodeForIndex(parent, m_feedList.data()); return node ? node->children().count() : 0; } QVariant Akregator::SubscriptionListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } const Akregator::TreeNode *const node = nodeForIndex(index, m_feedList.data()); if (!node) { return QVariant(); } switch (role) { case Qt::EditRole: case Qt::DisplayRole: switch (index.column()) { case TitleColumn: return node->title(); case UnreadCountColumn: return node->unread(); case TotalCountColumn: return node->totalCount(); } break; case Qt::ToolTipRole: { if (node->isGroup() || node->isAggregation()) { return node->title(); } const Feed *const feed = qobject_cast(node); if (!feed) { return QString(); } if (feed->fetchErrorOccurred()) { return i18n("Could not fetch feed: %1", errorCodeToString(feed->fetchErrorCode())); } return feed->title(); } case Qt::DecorationRole: { if (index.column() != TitleColumn) { return QVariant(); } const Feed *const feed = qobject_cast(node); - return feed && feed->isFetching() ? node->icon().pixmap(KIconLoader::SizeSmall, QIcon::Active) : node->icon(); + const auto iconSize = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize); + return feed && feed->isFetching() ? node->icon().pixmap(iconSize, QIcon::Active) : node->icon(); } case SubscriptionIdRole: return node->id(); case IsGroupRole: return node->isGroup(); case IsFetchableRole: return !node->isGroup() && !node->isAggregation(); case IsAggregationRole: return node->isAggregation(); case LinkRole: { const Feed *const feed = qobject_cast(node); return feed ? feed->xmlUrl() : QVariant(); } case IsOpenRole: { if (!node->isGroup()) { return false; } const Akregator::Folder *const folder = qobject_cast(node); Q_ASSERT(folder); return folder->isOpen(); } case HasUnreadRole: return node->unread() > 0; } return QVariant(); } QVariant Akregator::SubscriptionListModel::headerData(int section, Qt::Orientation, int role) const { if (role != Qt::DisplayRole) { return QVariant(); } switch (section) { case TitleColumn: return i18nc("Feedlist's column header", "Feeds"); case UnreadCountColumn: return i18nc("Feedlist's column header", "Unread"); case TotalCountColumn: return i18nc("Feedlist's column header", "Total"); } return QVariant(); } QModelIndex Akregator::SubscriptionListModel::parent(const QModelIndex &index) const { const Akregator::TreeNode *const node = nodeForIndex(index, m_feedList.data()); if (!node || !node->parent()) { return QModelIndex(); } const Akregator::Folder *parent = node->parent(); if (!parent->parent()) { return createIndex(0, 0, parent->id()); } const Akregator::Folder *const grandparent = parent->parent(); const int row = grandparent->indexOf(parent); Q_ASSERT(row != -1); return createIndex(row, 0, parent->id()); } QModelIndex Akregator::SubscriptionListModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { return (row == 0 && m_feedList) ? createIndex(row, column, m_feedList->allFeedsFolder()->id()) : QModelIndex(); } const Akregator::TreeNode *const parentNode = nodeForIndex(parent, m_feedList.data()); if (!parentNode) { return QModelIndex(); } const Akregator::TreeNode *const childNode = parentNode->childAt(row); return childNode ? createIndex(row, column, childNode->id()) : QModelIndex(); } QModelIndex SubscriptionListModel::indexForNode(const TreeNode *node) const { if (!node || !m_feedList) { return QModelIndex(); } const Folder *const parent = node->parent(); if (!parent) { return index(0, 0); } const int row = parent->indexOf(node); Q_ASSERT(row >= 0); const QModelIndex idx = index(row, 0, indexForNode(parent)); Q_ASSERT(idx.internalId() == node->id()); return idx; } void Akregator::SubscriptionListModel::subscriptionAdded(Akregator::TreeNode *subscription) { const Folder *const parent = subscription->parent(); const int row = parent ? parent->indexOf(subscription) : 0; Q_ASSERT(row >= 0); beginInsertRows(indexForNode(parent), row, row); endInsertRows(); } void Akregator::SubscriptionListModel::aboutToRemoveSubscription(Akregator::TreeNode *subscription) { qCDebug(AKREGATOR_LOG) << subscription->id(); const Folder *const parent = subscription->parent(); const int row = parent ? parent->indexOf(subscription) : -1; if (row < 0) { return; } beginRemoveRows(indexForNode(parent), row, row); m_beganRemoval = true; } void Akregator::SubscriptionListModel::subscriptionRemoved(TreeNode *subscription) { qCDebug(AKREGATOR_LOG) << subscription->id(); if (m_beganRemoval) { m_beganRemoval = false; endRemoveRows(); } } void Akregator::SubscriptionListModel::subscriptionChanged(TreeNode *node) { const QModelIndex idx = indexForNode(node); if (!idx.isValid()) { return; } Q_EMIT dataChanged(index(idx.row(), 0, idx.parent()), index(idx.row(), ColumnCount - 1, idx.parent())); } void SubscriptionListModel::fetchStarted(Akregator::Feed *node) { subscriptionChanged(node); } void SubscriptionListModel::fetched(Akregator::Feed *node) { subscriptionChanged(node); } void SubscriptionListModel::fetchError(Akregator::Feed *node) { subscriptionChanged(node); } void SubscriptionListModel::fetchAborted(Akregator::Feed *node) { subscriptionChanged(node); } void Akregator::FolderExpansionHandler::itemExpanded(const QModelIndex &idx) { setExpanded(idx, true); } void Akregator::FolderExpansionHandler::itemCollapsed(const QModelIndex &idx) { setExpanded(idx, false); } void Akregator::FolderExpansionHandler::setExpanded(const QModelIndex &idx, bool expanded) { if (!m_feedList || !m_model) { return; } Akregator::TreeNode *const node = m_feedList->findByID(nodeIdForIndex(idx)); if (!node || !node->isGroup()) { return; } Akregator::Folder *const folder = qobject_cast(node); Q_ASSERT(folder); folder->setOpen(expanded); } FolderExpansionHandler::FolderExpansionHandler(QObject *parent) : QObject(parent) , m_feedList() , m_model(nullptr) { } void FolderExpansionHandler::setModel(QAbstractItemModel *model) { m_model = model; } void FolderExpansionHandler::setFeedList(const QSharedPointer &feedList) { m_feedList = feedList; } Qt::ItemFlags SubscriptionListModel::flags(const QModelIndex &idx) const { const Qt::ItemFlags flags = QAbstractItemModel::flags(idx); if (!idx.isValid() || (idx.column() != TitleColumn)) { return flags; } if (!idx.parent().isValid()) { // the root folder is neither draggable nor editable return flags | Qt::ItemIsDropEnabled; } return flags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; } QStringList SubscriptionListModel::mimeTypes() const { QStringList types; types << QStringLiteral("text/uri-list") << AKREGATOR_TREENODE_MIMETYPE; return types; } QMimeData *SubscriptionListModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData; QList urls; for (const QModelIndex &i : indexes) { const QUrl url(i.data(LinkRole).toString()); if (!url.isEmpty()) { urls << url; } } mimeData->setUrls(urls); QByteArray idList; QDataStream idStream(&idList, QIODevice::WriteOnly); for (const QModelIndex &i : indexes) { if (i.isValid()) { idStream << i.data(SubscriptionIdRole).toInt(); } } mimeData->setData(AKREGATOR_TREENODE_MIMETYPE, idList); return mimeData; } bool SubscriptionListModel::setData(const QModelIndex &idx, const QVariant &value, int role) { if (!idx.isValid() || idx.column() != TitleColumn || role != Qt::EditRole) { return false; } const TreeNode *const node = nodeForIndex(idx, m_feedList.data()); if (!node) { return false; } RenameSubscriptionJob *job = new RenameSubscriptionJob(this); job->setSubscriptionId(node->id()); job->setName(value.toString()); job->start(); return true; } bool SubscriptionListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(column) if (action == Qt::IgnoreAction) { return true; } //if ( column != TitleColumn ) // return false; if (!data->hasFormat(AKREGATOR_TREENODE_MIMETYPE)) { return false; } const TreeNode *const droppedOnNode = qobject_cast(nodeForIndex(parent, m_feedList.data())); if (!droppedOnNode) { return false; } const Folder *const destFolder = droppedOnNode->isGroup() ? qobject_cast(droppedOnNode) : droppedOnNode->parent(); if (!destFolder) { return false; } QByteArray idData = data->data(AKREGATOR_TREENODE_MIMETYPE); QList ids; QDataStream stream(&idData, QIODevice::ReadOnly); while (!stream.atEnd()) { int id; stream >> id; ids << id; } //don't drop nodes into their own subtree for (const int id : qAsConst(ids)) { const Folder *const asFolder = qobject_cast(m_feedList->findByID(id)); if (asFolder && (asFolder == destFolder || asFolder->subtreeContains(destFolder))) { return false; } } const TreeNode *const after = droppedOnNode->isGroup() ? destFolder->childAt(row) : droppedOnNode; for (const int id : qAsConst(ids)) { const TreeNode *const node = m_feedList->findByID(id); if (!node) { continue; } MoveSubscriptionJob *job = new MoveSubscriptionJob(this); job->setSubscriptionId(node->id()); job->setDestination(destFolder->id(), after ? after->id() : -1); job->start(); } return true; } diff --git a/src/tabwidget.cpp b/src/tabwidget.cpp index 64eb482c..e1ebb62d 100644 --- a/src/tabwidget.cpp +++ b/src/tabwidget.cpp @@ -1,520 +1,519 @@ /* This file is part of Akregator. Copyright (C) 2004 Sashmit Bhaduri This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "tabwidget.h" #include #include #include #include #include #include #include #include #include "akregator_debug.h" #include #include #include #include #include #include -#include #include "actionmanager.h" #include "akregatorconfig.h" #include "frame.h" #include "framemanager.h" #include "kernel.h" #include "openurlrequest.h" #include "utils/temporaryvalue.h" using namespace Akregator; class Q_DECL_HIDDEN TabWidget::Private { private: TabWidget *const q; public: explicit Private(TabWidget *qq) : q(qq) { } QHash frames; QHash framesById; int currentMaxLength = 30; QWidget *currentItem = nullptr; QToolButton *tabsClose = nullptr; QWidget *selectedWidget() const { return (currentItem && q->indexOf(currentItem) != -1) ? currentItem : q->currentWidget(); } int tabBarWidthForMaxChars(int maxLength); void setTitle(const QString &title, QWidget *sender); void updateTabBarVisibility(); Frame *currentFrame(); }; void TabWidget::Private::updateTabBarVisibility() { const bool tabBarIsHidden = ((q->count() <= 1) && !Settings::alwaysShowTabBar()); if (tabBarIsHidden) { q->tabBar()->hide(); } else { q->tabBar()->show(); } if (q->count() >= 1 && Settings::closeButtonOnTabs()) { q->tabBar()->tabButton(0, QTabBar::RightSide)->hide(); } } TabWidget::TabWidget(QWidget *parent) : QTabWidget(parent) , d(new Private(this)) { setMinimumSize(250, 150); setMovable(false); setDocumentMode(true); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &TabWidget::customContextMenuRequested, this, &TabWidget::slotTabContextMenuRequest); connect(this, &TabWidget::currentChanged, this, &TabWidget::slotTabChanged); connect(this, &QTabWidget::tabCloseRequested, this, &TabWidget::slotCloseRequest); setTabsClosable(Settings::closeButtonOnTabs()); d->tabsClose = new QToolButton(this); connect(d->tabsClose, &QToolButton::clicked, this, &TabWidget::slotRemoveCurrentFrame); d->tabsClose->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); d->tabsClose->setEnabled(false); d->tabsClose->adjustSize(); d->tabsClose->setToolTip(i18n("Close the current tab")); #ifndef QT_NO_ACCESSIBILITY d->tabsClose->setAccessibleName(i18n("Close tab")); #endif setCornerWidget(d->tabsClose, Qt::TopRightCorner); d->updateTabBarVisibility(); } TabWidget::~TabWidget() { delete d; } void TabWidget::slotTabContextMenuRequest(const QPoint &pos) { QTabBar *bar = tabBar(); if (count() <= 1) { return; } const int indexBar = bar->tabAt(bar->mapFrom(this, pos)); if (indexBar == -1) { return; } QMenu menu(this); const int countTab = (count() > 1); QAction *detachTab = menu.addAction(i18nc("@action:inmenu", "Detach Tab")); detachTab->setEnabled((indexBar != 0) && countTab); detachTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); menu.addSeparator(); QAction *closeTab = menu.addAction(i18nc("@action:inmenu", "Close Tab")); closeTab->setEnabled((indexBar != 0) && countTab); closeTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); QAction *allOther = menu.addAction(i18nc("@action:inmenu", "Close All Other Tabs")); allOther->setEnabled(countTab); allOther->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other"))); QAction *allTab = menu.addAction(i18nc("@action:inmenu", "Close All Tabs")); allTab->setEnabled(countTab); allTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); QAction *action = menu.exec(mapToGlobal(pos)); if (action == allOther) { // Close all other tabs slotCloseAllTabExcept(indexBar); } else if (action == closeTab) { slotCloseRequest(indexBar); } else if (action == allTab) { slotCloseAllTab(); } else if (action == detachTab) { slotDetachTab(indexBar); } } void TabWidget::closeAllTabExcept(int index) { //Don't close first tab for (int i = count() - 1; i > 0; --i) { if (i == index) { continue; } slotCloseRequest(i); } } void TabWidget::slotCloseAllTabExcept(int index) { closeAllTabExcept(index); } void TabWidget::slotCloseAllTab() { closeAllTabExcept(); } void TabWidget::slotSettingsChanged() { if (tabsClosable() != Settings::closeButtonOnTabs()) { setTabsClosable(Settings::closeButtonOnTabs()); } d->updateTabBarVisibility(); } void TabWidget::slotNextTab() { setCurrentIndex((currentIndex() + 1) % count()); } void TabWidget::slotPreviousTab() { if (currentIndex() == 0) { setCurrentIndex(count() - 1); } else { setCurrentIndex(currentIndex() - 1); } } void TabWidget::slotSelectFrame(int frameId) { Frame *frame = d->framesById.value(frameId); if (frame && frame != d->currentFrame()) { setCurrentWidget(frame); frame->setFocus(); } } void TabWidget::slotAddFrame(Frame *frame) { if (!frame) { return; } d->frames.insert(frame, frame); d->framesById.insert(frame->id(), frame); addTab(frame, frame->title()); connect(frame, &Frame::signalTitleChanged, this, &TabWidget::slotSetTitle); slotSetTitle(frame, frame->title()); } Frame *TabWidget::Private::currentFrame() { QWidget *w = q->currentWidget(); Q_ASSERT(frames.value(w)); return w ? frames.value(w) : nullptr; } void TabWidget::slotZoomChanged(qreal value) { if (!d->currentFrame()) { return; } Q_EMIT signalZoomChangedInFrame(d->currentFrame()->id(), value); } void TabWidget::slotTabChanged(int index) { Frame *frame = d->frames.value(widget(index)); d->tabsClose->setEnabled(frame && frame->isRemovable()); Q_EMIT signalCurrentFrameChanged(frame ? frame->id() : -1); } void TabWidget::tabInserted(int) { d->updateTabBarVisibility(); } void TabWidget::tabRemoved(int) { d->updateTabBarVisibility(); } void TabWidget::slotRemoveCurrentFrame() { Frame *const frame = d->currentFrame(); if (frame) { Q_EMIT signalRemoveFrameRequest(frame->id()); } } void TabWidget::slotRemoveFrame(int frameId) { if (!d->framesById.contains(frameId)) { return; } Frame *f = d->framesById.value(frameId); d->frames.remove(f); d->framesById.remove(frameId); f->disconnect(this); removeTab(indexOf(f)); Q_EMIT signalRemoveFrameRequest(f->id()); if (d->currentFrame()) { d->setTitle(d->currentFrame()->title(), currentWidget()); } } // copied wholesale from KonqFrameTabs int TabWidget::Private::tabBarWidthForMaxChars(int maxLength) { int hframe; QStyleOption o; hframe = q->tabBar()->style()->pixelMetric(QStyle::PM_TabBarTabHSpace, &o, q); QFontMetrics fm = q->tabBar()->fontMetrics(); int x = 0; for (int i = 0; i < q->count(); ++i) { Frame *f = frames.value(q->widget(i)); if (!f) { continue; // frames is out of sync, e.g. because tabInserted wasn't called yet - #185597 } QString newTitle = f->title(); if (newTitle.length() > maxLength) { newTitle = newTitle.left(maxLength - 3) + QLatin1String("..."); } int lw = fm.boundingRect(newTitle).width(); int iw = q->tabBar()->tabIcon(i).pixmap(q->tabBar()->style()->pixelMetric( QStyle::PM_SmallIconSize), QIcon::Normal ).width() + 4; x += (q->tabBar()->style()->sizeFromContents(QStyle::CT_TabBarTab, &o, QSize(qMax(lw + hframe + iw, QApplication::globalStrut().width()), 0), q)).width(); } return x; } void TabWidget::slotSetTitle(Frame *frame, const QString &title) { d->setTitle(title, frame); } void TabWidget::slotWebPageMutedOrAudibleChanged(Akregator::Frame *frame, bool isAudioMuted, bool wasRecentlyAudible) { Q_UNUSED(wasRecentlyAudible); const int idx = indexOf(frame); if (idx < 0) { return; } if (isAudioMuted) { setTabIcon(idx, QIcon::fromTheme(QStringLiteral("audio-volume-muted"))); } else { setTabIcon(idx, frame->icon()); } } void TabWidget::slotSetIcon(Akregator::Frame *frame, const QIcon &icon) { const int idx = indexOf(frame); if (idx < 0) { return; } frame->setIcon(icon); setTabIcon(idx, icon); } void TabWidget::Private::setTitle(const QString &title, QWidget *sender) { int senderIndex = q->indexOf(sender); q->setTabToolTip(senderIndex, QString()); int lcw = 0, rcw = 0; int tabBarHeight = q->tabBar()->sizeHint().height(); QWidget *leftCorner = q->cornerWidget(Qt::TopLeftCorner); if (leftCorner && leftCorner->isVisible()) { lcw = qMax(leftCorner->width(), tabBarHeight); } QWidget *rightCorner = q->cornerWidget(Qt::TopRightCorner); if (rightCorner && rightCorner->isVisible()) { rcw = qMax(rightCorner->width(), tabBarHeight); } int maxTabBarWidth = q->width() - lcw - rcw; int newMaxLength = 30; for (; newMaxLength > 3; newMaxLength--) { if (tabBarWidthForMaxChars(newMaxLength) < maxTabBarWidth) { break; } } QString newTitle = title; if (newTitle.length() > newMaxLength) { q->setTabToolTip(senderIndex, newTitle); newTitle = newTitle.left(newMaxLength - 3) + QLatin1String("..."); } newTitle.replace(QLatin1Char('&'), QStringLiteral("&&")); if (q->tabText(senderIndex) != newTitle) { q->setTabText(senderIndex, newTitle); } if (newMaxLength != currentMaxLength) { for (int i = 0; i < q->count(); ++i) { Frame *f = frames.value(q->widget(i)); if (!f) { continue; // frames is out of sync, e.g. because tabInserted wasn't called yet - #185597 } newTitle = f->title(); int index = q->indexOf(q->widget(i)); q->setTabToolTip(index, QString()); if (newTitle.length() > newMaxLength) { q->setTabToolTip(index, newTitle); newTitle = newTitle.left(newMaxLength - 3) + QLatin1String("..."); } newTitle.replace(QLatin1Char('&'), QStringLiteral("&&")); if (newTitle != q->tabText(index)) { q->setTabText(index, newTitle); } } currentMaxLength = newMaxLength; } } void TabWidget::slotDetachTab(int index) { QWidget *w = widget(index); Frame *frame = d->frames.value(w); if (frame && frame->url().isValid() && frame->isRemovable()) { OpenUrlRequest request; request.setUrl(frame->url()); request.setOptions(OpenUrlRequest::ExternalBrowser); Q_EMIT signalOpenUrlRequest(request); slotCloseRequest(index); } } void TabWidget::slotTextToSpeech() { Q_EMIT signalTextToSpeechInFrame(d->currentFrame()->id()); } void TabWidget::slotFindTextInHtml() { Q_EMIT signalFindTextInFrame(d->currentFrame()->id()); } void TabWidget::slotCopyLinkAddress() { Q_EMIT signalCopyLinkAsInFrame(d->currentFrame()->id()); } void TabWidget::slotSaveLinkAs() { Q_EMIT signalSaveLinkAsInFrame(d->currentFrame()->id()); } void TabWidget::slotPrintPreview() { Q_EMIT signalPrintPreviewInFrame(d->currentFrame()->id()); } void TabWidget::slotPrint() { Q_EMIT signalPrintInFrame(d->currentFrame()->id()); } void TabWidget::slotCopy() { Q_EMIT signalCopyInFrame(d->currentFrame()->id()); } void TabWidget::slotSaveImageOnDisk() { Q_EMIT signalSaveImageOnDisk(d->currentFrame()->id()); } void TabWidget::slotUnMute() { Q_EMIT signalMute(d->currentFrame()->id(), false); } void TabWidget::slotMute() { Q_EMIT signalMute(d->currentFrame()->id(), true); } void TabWidget::slotCopyImageLocation() { Q_EMIT signalCopyImageLocation(d->currentFrame()->id()); } void TabWidget::slotCloseTab() { QWidget *widget = d->selectedWidget(); Frame *frame = d->frames.value(widget); if (frame == nullptr || !frame->isRemovable()) { return; } Q_EMIT signalRemoveFrameRequest(frame->id()); } void TabWidget::slotReloadAllTabs() { for (Frame *frame : qAsConst(d->frames)) { frame->slotReload(); } } void TabWidget::slotCloseRequest(int index) { QWidget *w = widget(index); if (d->frames.value(w)) { Q_EMIT signalRemoveFrameRequest(d->frames.value(w)->id()); } } void TabWidget::slotActivateTab() { setCurrentIndex(sender()->objectName().rightRef(2).toInt() - 1); }