diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b9e5b8cf..f4b9833f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,148 +1,148 @@ # SPDX-License-Identifier: CC0-1.0 # SPDX-FileCopyrightText: none cmake_minimum_required(VERSION 3.16 FATAL_ERROR) -set(PIM_VERSION "6.0.41") +set(PIM_VERSION "6.0.42") project(libkleo VERSION ${PIM_VERSION}) set(KF_MIN_VERSION "5.248.0") if (WIN32) set(KF6_WANT_VERSION "5.104.0") add_compile_definitions(GPG_ERR_ENABLE_GETTEXT_MACROS=1) else () set(KF6_WANT_VERSION ${KF_MIN_VERSION}) endif () find_package(ECM ${KF6_WANT_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMGenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) include(ECMDeprecationSettings) include(ECMAddQch) include(KDEClangFormat) include(KDEGitCommitHooks) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") set(LIBKLEO_LIB_VERSION ${PIM_VERSION}) set(QT_REQUIRED_VERSION "6.6.0") set(GPGME_REQUIRED_VERSION "1.20.0") set(KTEXTADDONS_MIN_VERSION "1.5.1") set(GPG_ERROR_REQUIRED_VERSION "1.36") find_package(Qt6 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets) find_package(KF6I18n ${KF6_WANT_VERSION} CONFIG REQUIRED) find_package(KF6Config ${KF6_WANT_VERSION} CONFIG REQUIRED) find_package(KF6WidgetsAddons ${KF6_WANT_VERSION} CONFIG REQUIRED) find_package(KF6ColorScheme ${KF6_WANT_VERSION} CONFIG REQUIRED) find_package(KF6Completion ${KF6_WANT_VERSION} CONFIG REQUIRED) find_package(KF6CoreAddons ${KF6_WANT_VERSION} CONFIG REQUIRED) find_package(KF6Codecs ${KF6_WANT_VERSION} CONFIG REQUIRED) find_package(KF6ItemModels ${KF6_WANT_VERSION} CONFIG REQUIRED) find_package(KF6TextCustomEditor ${KTEXTADDONS_MIN_VERSION} CONFIG) find_package(Gpgmepp ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) set_package_properties(Gpgmepp PROPERTIES DESCRIPTION "GpgME++ Library" URL "https://www.gnupg.org" TYPE REQUIRED PURPOSE "GpgME++ is required for OpenPGP support") message(STATUS "GpgME++ Version ${Gpgmepp_VERSION}") set(QGPGME_NAME "QGpgmeQt6") if (Gpgmepp_VERSION VERSION_GREATER_EQUAL "1.23.0") set(GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE 1) endif() find_package(${QGPGME_NAME} ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) find_package(LibGpgError ${GPG_ERROR_REQUIRED_VERSION} REQUIRED) set_package_properties(LibGpgError PROPERTIES TYPE REQUIRED ) find_package(Boost 1.34.0) set_package_properties(Boost PROPERTIES DESCRIPTION "Boost C++ Libraries" URL "https://www.boost.org" TYPE REQUIRED PURPOSE "Boost is required for building most KDEPIM applications") ecm_setup_version(PROJECT VARIABLE_PREFIX LIBKLEO VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/libkleo_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPim6LibkleoConfigVersion.cmake" SOVERSION 6 ) ########### Targets ########### ecm_set_disabled_deprecation_versions(QT 5.15.2 KF 5.248.0) remove_definitions(-DQT_NO_FOREACH) add_definitions(-DQT_NO_EMIT) ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KPim6Libkleo") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libkleo_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/Libkleo COMPONENT Devel ) include_directories(${CMAKE_CURRENT_BINARY_DIR}) option(USE_UNITY_CMAKE_SUPPORT "Use UNITY cmake support (speedup compile time)" OFF) set(COMPILE_WITH_UNITY_CMAKE_SUPPORT OFF) if (USE_UNITY_CMAKE_SUPPORT) set(COMPILE_WITH_UNITY_CMAKE_SUPPORT ON) set(UNITY_BUILD ON) endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-libkleo.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-libkleo.h) add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() ecm_qt_install_logging_categories( EXPORT LIBKLEO FILE libkleo.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) ki18n_install(po) if (BUILD_QCH) ecm_install_qch_export( TARGETS KPim6Libkleo_QCH FILE KPim6LibkleoQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KPim6LibkleoQchTargets.cmake\")") endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KPimLibkleoConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KPim6LibkleoConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KPim6LibkleoConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KPim6LibkleoConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KPim6LibkleoTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KPim6LibkleoTargets.cmake NAMESPACE KPim6::) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) # add clang-format target for all our real source files file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.c) kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ab6249dde..bc348c525 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,397 +1,397 @@ # SPDX-License-Identifier: CC0-1.0 # SPDX-FileCopyrightText: none # target_include_directories does not handle empty include paths include_directories( ${Boost_INCLUDE_DIRS} ${GPGME_INCLUDES} ) add_definitions(-DTRANSLATION_DOMAIN=\"libkleopatra6\") #add_definitions( -DQT_NO_CAST_FROM_ASCII ) #add_definitions( -DQT_NO_CAST_TO_ASCII ) kde_enable_exceptions() add_definitions( -DGPGMEPP_ERR_SOURCE_DEFAULT=13 ) # 13 is GPG_ERR_SOURCE_KLEO, even if gpg-error's too old to know about add_subdirectory( pics ) add_library(KPim6Libkleo) add_library(KPim6::Libkleo ALIAS KPim6Libkleo) ########### next target ############### target_sources(KPim6Libkleo PRIVATE kleo/auditlogentry.cpp kleo/auditlogentry.h kleo/checksumdefinition.cpp kleo/checksumdefinition.h kleo/debug.cpp kleo/debug.h kleo/defaultkeyfilter.cpp kleo/defaultkeyfilter.h kleo/defaultkeygenerationjob.cpp kleo/defaultkeygenerationjob.h kleo/docaction.cpp kleo/docaction.h kleo/dn.cpp kleo/dn.h kleo/enum.cpp kleo/enum.h kleo/expirychecker.cpp kleo/expirychecker.h kleo/expirycheckerconfig.cpp kleo/expirycheckerconfig.h kleo/expirycheckersettings.cpp kleo/expirycheckersettings.h kleo/kconfigbasedkeyfilter.cpp kleo/kconfigbasedkeyfilter.h kleo/keyfilter.h kleo/keyfiltermanager.cpp kleo/keyfiltermanager.h kleo/keygroup.cpp kleo/keygroup.h kleo/keygroupconfig.cpp kleo/keygroupconfig.h kleo/keygroupimportexport.cpp kleo/keygroupimportexport.h kleo/keyresolver.cpp kleo/keyresolver.h kleo/keyresolvercore.cpp kleo/keyresolvercore.h kleo/keyserverconfig.cpp kleo/keyserverconfig.h kleo/kleoexception.cpp kleo/kleoexception.h kleo/oidmap.cpp kleo/oidmap.h kleo/predicates.h kleo/stl_util.h models/keycache.cpp models/keycache.h models/keycache_p.h models/keylist.h models/keylistmodel.cpp models/keylistmodel.h models/keylistmodelinterface.cpp models/keylistmodelinterface.h models/keylistsortfilterproxymodel.cpp models/keylistsortfilterproxymodel.h models/keyrearrangecolumnsproxymodel.cpp models/keyrearrangecolumnsproxymodel.h models/subkeylistmodel.cpp models/subkeylistmodel.h models/useridlistmodel.cpp models/useridlistmodel.h models/useridproxymodel.cpp models/useridproxymodel.h utils/algorithm.h utils/assuan.cpp utils/assuan.h utils/chrono.h utils/classify.cpp utils/classify.h utils/compat.cpp utils/compat.h utils/compliance.cpp utils/compliance.h utils/cryptoconfig.cpp utils/cryptoconfig.h utils/cryptoconfig_p.h utils/filesystemwatcher.cpp utils/filesystemwatcher.h utils/formatting.cpp utils/formatting.h utils/gnupg-registry.c utils/gnupg-registry.h utils/gnupg.cpp utils/gnupg.h utils/hex.cpp utils/hex.h utils/keyhelpers.cpp utils/keyhelpers.h utils/keyusage.h utils/qtstlhelpers.cpp utils/qtstlhelpers.h utils/scdaemon.cpp utils/scdaemon.h utils/stringutils.cpp utils/stringutils.h utils/systeminfo.cpp utils/systeminfo.h utils/test.cpp utils/test.h utils/uniquelock.cpp utils/uniquelock.h ) ecm_qt_declare_logging_category(KPim6Libkleo HEADER libkleo_debug.h IDENTIFIER LIBKLEO_LOG CATEGORY_NAME org.kde.pim.libkleo DESCRIPTION "libkleo (kleo_core)" EXPORT LIBKLEO ) target_sources(KPim6Libkleo PRIVATE ui/adjustingscrollarea.cpp ui/adjustingscrollarea.h ui/auditlogviewer.cpp ui/auditlogviewer.h ui/cryptoconfigentryreaderport.cpp ui/cryptoconfigentryreaderport_p.h ui/cryptoconfigmodule.cpp ui/cryptoconfigmodule.h ui/cryptoconfigmodule_p.h ui/directoryserviceswidget.cpp ui/directoryserviceswidget.h ui/dnattributeorderconfigwidget.cpp ui/dnattributeorderconfigwidget.h ui/editdirectoryservicedialog.cpp ui/editdirectoryservicedialog.h ui/filenamerequester.cpp ui/filenamerequester.h ui/messagebox.cpp ui/messagebox.h - ui/navigatabletreeview.cpp - ui/navigatabletreeview.h - ui/navigatabletreewidget.cpp - ui/navigatabletreewidget.h + ui/treeview.cpp + ui/treeview.h + ui/treewidget.cpp + ui/treewidget.h ui/progressbar.cpp ui/progressbar.h ui/progressdialog.cpp ui/progressdialog.h ui/readerportselection.cpp ui/readerportselection.h ) ecm_qt_declare_logging_category(KPim6Libkleo HEADER kleo_ui_debug.h IDENTIFIER KLEO_UI_LOG CATEGORY_NAME org.kde.pim.kleo_ui DESCRIPTION "libkleo (kleo_ui)" OLD_CATEGORY_NAMES log_kleo_ui EXPORT LIBKLEO ) target_sources(KPim6Libkleo PRIVATE # make this a separate lib. ui/keyapprovaldialog.cpp ui/keyapprovaldialog.h ui/keylistview.cpp ui/keylistview.h ui/keyrequester.cpp ui/keyrequester.h ui/keyselectioncombo.cpp ui/keyselectioncombo.h ui/keyselectiondialog.cpp ui/keyselectiondialog.h ui/newkeyapprovaldialog.cpp ui/newkeyapprovaldialog.h ) if(MINGW) # we do not care about different signedness of passed pointer arguments set_source_files_properties(utils/gnupg-registry.c PROPERTIES COMPILE_OPTIONS "-Wno-pointer-sign") endif() target_link_libraries(KPim6Libkleo PUBLIC Gpgmepp PRIVATE Qt::Widgets KF6::I18n KF6::Completion KF6::ConfigCore KF6::ColorScheme KF6::ConfigGui KF6::CoreAddons KF6::WidgetsAddons KF6::ItemModels KF6::Codecs LibGpgError::LibGpgError) target_link_libraries(KPim6Libkleo PUBLIC QGpgmeQt6) # Boost::headers may not be available for old versions of Boost if (TARGET Boost::headers) target_link_libraries(KPim6Libkleo PRIVATE Boost::headers) endif() if (TARGET KF6::TextCustomEditor) add_definitions(-DHAVE_PIMTEXTEDIT) target_link_libraries(KPim6Libkleo PRIVATE KF6::TextCustomEditor) endif() if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) set_target_properties(KPim6Libkleo PROPERTIES UNITY_BUILD ON) endif() ecm_generate_export_header(KPim6Libkleo BASE_NAME kleo VERSION ${PIM_VERSION} DEPRECATED_BASE_VERSION 0 DEPRECATION_VERSIONS 5.23 ) if(WIN32) target_link_libraries(KPim6Libkleo ${GPGME_VANILLA_LIBRARIES} ) endif() set_target_properties(KPim6Libkleo PROPERTIES VERSION ${LIBKLEO_VERSION} SOVERSION ${LIBKLEO_SOVERSION} EXPORT_NAME Libkleo ) install(TARGETS KPim6Libkleo EXPORT KPim6LibkleoTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) target_include_directories(KPim6Libkleo INTERFACE "$") target_include_directories(KPim6Libkleo PUBLIC "$") ecm_generate_headers(libkleo_CamelCase_HEADERS HEADER_NAMES AuditLogEntry ChecksumDefinition Debug DefaultKeyFilter DefaultKeyGenerationJob DocAction Dn Enum ExpiryChecker ExpiryCheckerConfig ExpiryCheckerSettings KConfigBasedKeyFilter KeyFilter KeyFilterManager KeyGroup KeyGroupConfig KeyGroupImportExport KeyResolver KeyResolverCore KeyserverConfig KleoException OidMap Predicates Stl_Util REQUIRED_HEADERS libkleo_HEADERS PREFIX Libkleo RELATIVE kleo ) ecm_generate_headers(libkleo_CamelCase_models_HEADERS HEADER_NAMES KeyCache KeyList KeyListModel KeyListModelInterface KeyListSortFilterProxyModel KeyRearrangeColumnsProxyModel SubkeyListModel UserIDListModel UserIDProxyModel REQUIRED_HEADERS libkleo_models_HEADERS PREFIX Libkleo RELATIVE models ) ecm_generate_headers(libkleo_CamelCase_utils_HEADERS HEADER_NAMES Algorithm Assuan Chrono Classify Compat Compliance CryptoConfig FileSystemWatcher Formatting GnuPG Hex KeyHelpers KeyUsage QtStlHelpers SCDaemon StringUtils SystemInfo Test UniqueLock REQUIRED_HEADERS libkleo_utils_HEADERS PREFIX Libkleo RELATIVE utils ) ecm_generate_headers(libkleo_CamelCase_ui_HEADERS HEADER_NAMES AdjustingScrollArea AuditLogViewer CryptoConfigModule DNAttributeOrderConfigWidget DirectoryServicesWidget EditDirectoryServiceDialog FileNameRequester KeyApprovalDialog KeyListView KeyRequester KeySelectionCombo KeySelectionDialog MessageBox - NavigatableTreeView - NavigatableTreeWidget + TreeView + TreeWidget NewKeyApprovalDialog ProgressDialog ReaderPortSelection REQUIRED_HEADERS libkleo_ui_HEADERS PREFIX Libkleo RELATIVE ui ) kconfig_add_kcfg_files(KPim6Libkleo kcfg/expirycheckerconfigbase.kcfgc kcfg/classifyconfig.kcfgc ) install(FILES ${libkleo_CamelCase_HEADERS} ${libkleo_CamelCase_models_HEADERS} ${libkleo_CamelCase_ui_HEADERS} ${libkleo_CamelCase_utils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/Libkleo/Libkleo COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/expirycheckerconfigbase.h ${CMAKE_CURRENT_BINARY_DIR}/kleo_export.h ${libkleo_HEADERS} ${libkleo_models_HEADERS} ${libkleo_ui_HEADERS} ${libkleo_utils_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/classifyconfig.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/Libkleo/libkleo COMPONENT Devel ) if ( WIN32 ) install ( FILES libkleopatrarc-win32.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) else () install ( FILES libkleopatrarc.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) endif () if (BUILD_QCH) ecm_add_qch( KPim6Libkleo_QCH NAME KPim6Libkleo BASE_NAME KPim6Libkleo VERSION ${PIM_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${libkleo_HEADERS} ${libkleo_models_HEADERS} ${libkleo_ui_HEADERS} ${libkleo_utils_HEADERS} #MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" #IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics" LINK_QCHS Qt6Core_QCH Qt6Gui_QCH Qt6Widgets_QCH INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR} BLANK_MACROS KLEO_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() diff --git a/src/ui/keylistview.cpp b/src/ui/keylistview.cpp index 0c838a468..874e332e2 100644 --- a/src/ui/keylistview.cpp +++ b/src/ui/keylistview.cpp @@ -1,566 +1,566 @@ /* keylistview.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keylistview.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; static const int updateDelayMilliSecs = 500; class Q_DECL_HIDDEN KeyListView::KeyListViewPrivate { public: KeyListViewPrivate() : updateTimer(nullptr) { } std::vector keyBuffer; QTimer *updateTimer = nullptr; std::map itemMap; }; // a list of signals where we want to replace QListViewItem with // Kleo:KeyListViewItem: static const struct { const char *source; const char *target; } signalReplacements[] = { { SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), SLOT(slotEmitDoubleClicked(QTreeWidgetItem *, int)), }, { SIGNAL(itemSelectionChanged()), SLOT(slotEmitSelectionChanged()), }, { SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotEmitContextMenu(QPoint)), }, }; static const int numSignalReplacements = sizeof signalReplacements / sizeof *signalReplacements; KeyListView::KeyListView(const ColumnStrategy *columnStrategy, const DisplayStrategy *displayStrategy, QWidget *parent, Qt::WindowFlags f) - : NavigatableTreeWidget(parent) + : TreeWidget(parent) , mColumnStrategy(columnStrategy) , mDisplayStrategy(displayStrategy) , mHierarchical(false) , d(new KeyListViewPrivate()) { setWindowFlags(f); setContextMenuPolicy(Qt::CustomContextMenu); d->updateTimer = new QTimer(this); d->updateTimer->setSingleShot(true); connect(d->updateTimer, &QTimer::timeout, this, &KeyListView::slotUpdateTimeout); if (!columnStrategy) { qCWarning(KLEO_UI_LOG) << "Kleo::KeyListView: need a column strategy to work with!"; return; } const QFontMetrics fm = fontMetrics(); for (int col = 0; !columnStrategy->title(col).isEmpty(); ++col) { headerItem()->setText(col, columnStrategy->title(col)); header()->resizeSection(col, columnStrategy->width(col, fm)); header()->setSectionResizeMode(col, columnStrategy->resizeMode(col)); } setAllColumnsShowFocus(false); for (int i = 0; i < numSignalReplacements; ++i) { connect(this, signalReplacements[i].source, signalReplacements[i].target); } this->setToolTip(QString()); viewport()->setToolTip(QString()); // make double sure :) } KeyListView::~KeyListView() { d->updateTimer->stop(); // need to clear here, since in ~QListView, our children won't have // a valid listView() pointing to us anymore, and their dtors try to // unregister from us. clear(); Q_ASSERT(d->itemMap.size() == 0); // need to delete the tooltip ourselves, as ~QToolTip isn't virtual :o delete mColumnStrategy; mColumnStrategy = nullptr; delete mDisplayStrategy; mDisplayStrategy = nullptr; } void KeyListView::takeItem(QTreeWidgetItem *qlvi) { // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListView::takeItem(" << qlvi <<" )"; if (auto *item = lvi_cast(qlvi)) { deregisterItem(item); } takeTopLevelItem(indexOfTopLevelItem(qlvi)); } void KeyListView::setHierarchical(bool hier) { if (hier == mHierarchical) { return; } mHierarchical = hier; if (hier) { gatherScattered(); } else { scatterGathered(firstChild()); } } void KeyListView::slotAddKey(const GpgME::Key &key) { if (key.isNull()) { return; } d->keyBuffer.push_back(key); if (!d->updateTimer->isActive()) { d->updateTimer->start(updateDelayMilliSecs); } } void KeyListView::slotUpdateTimeout() { if (d->keyBuffer.empty()) { return; } const bool wasUpdatesEnabled = viewport()->updatesEnabled(); if (wasUpdatesEnabled) { viewport()->setUpdatesEnabled(false); } qCDebug(KLEO_UI_LOG) << "Kleo::KeyListView::slotUpdateTimeout(): processing" << d->keyBuffer.size() << "items en block"; if (hierarchical()) { for (std::vector::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) { doHierarchicalInsert(*it); } gatherScattered(); } else { for (std::vector::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) { (void)new KeyListViewItem(this, *it); } } if (wasUpdatesEnabled) { viewport()->setUpdatesEnabled(true); } d->keyBuffer.clear(); } void KeyListView::clear() { d->updateTimer->stop(); d->keyBuffer.clear(); while (QTreeWidgetItem *item = topLevelItem(0)) { delete item; } QTreeWidget::clear(); } void KeyListView::registerItem(KeyListViewItem *item) { // qCDebug(KLEO_UI_LOG) <<"registerItem(" << item <<" )"; if (!item) { return; } const QByteArray fpr = item->key().primaryFingerprint(); if (!fpr.isEmpty()) { d->itemMap.insert(std::make_pair(fpr, item)); } } void KeyListView::deregisterItem(const KeyListViewItem *item) { // qCDebug(KLEO_UI_LOG) <<"deregisterItem( KeyLVI:" << item <<" )"; if (!item) { return; } auto it = d->itemMap.find(item->key().primaryFingerprint()); if (it == d->itemMap.end()) { return; } // This Q_ASSERT triggers, though it shouldn't. Print some more // information when it happens. // Q_ASSERT( it->second == item ); if (it->second != item) { qCWarning(KLEO_UI_LOG) << "deregisterItem:" << "item " << item->key().primaryFingerprint() // << "it->second" << (it->second ? it->second->key().primaryFingerprint() : "is null"); return; } d->itemMap.erase(it); } void KeyListView::doHierarchicalInsert(const GpgME::Key &key) { const QByteArray fpr = key.primaryFingerprint(); if (fpr.isEmpty()) { return; } KeyListViewItem *item = nullptr; if (!key.isRoot()) { if (KeyListViewItem *parent = itemByFingerprint(key.chainID())) { item = new KeyListViewItem(parent, key); parent->setExpanded(true); } } if (!item) { item = new KeyListViewItem(this, key); // top-level (for now) } d->itemMap.insert(std::make_pair(fpr, item)); } void KeyListView::gatherScattered() { KeyListViewItem *item = firstChild(); while (item) { KeyListViewItem *cur = item; item = item->nextSibling(); if (cur->key().isRoot()) { continue; } if (KeyListViewItem *parent = itemByFingerprint(cur->key().chainID())) { // found a new parent... // ### todo: optimize by suppressing removing/adding the item to the itemMap... takeTopLevelItem(indexOfTopLevelItem(cur)); parent->addChild(cur); parent->setExpanded(true); } } } void KeyListView::scatterGathered(KeyListViewItem *start) { KeyListViewItem *item = start; while (item) { KeyListViewItem *cur = item; item = item->nextSibling(); scatterGathered(lvi_cast(cur->child(0))); Q_ASSERT(cur->childCount() == 0); // ### todo: optimize by suppressing removing/adding the item to the itemMap... if (cur->parent()) { static_cast(cur->parent())->takeItem(cur); } else { takeItem(cur); } addTopLevelItem(cur); } } KeyListViewItem *KeyListView::itemByFingerprint(const QByteArray &s) const { if (s.isEmpty()) { return nullptr; } const std::map::const_iterator it = d->itemMap.find(s); if (it == d->itemMap.end()) { return nullptr; } return it->second; } void KeyListView::slotRefreshKey(const GpgME::Key &key) { const char *fpr = key.primaryFingerprint(); if (!fpr) { return; } if (KeyListViewItem *item = itemByFingerprint(fpr)) { item->setKey(key); } else { // none found -> add it slotAddKey(key); } } // slots for the emission of covariant Q_SIGNALS: void KeyListView::slotEmitDoubleClicked(QTreeWidgetItem *item, int col) { if (!item || lvi_cast(item)) { Q_EMIT doubleClicked(static_cast(item), col); } } void KeyListView::slotEmitReturnPressed(QTreeWidgetItem *item) { if (!item || lvi_cast(item)) { Q_EMIT returnPressed(static_cast(item)); } } void KeyListView::slotEmitSelectionChanged() { Q_EMIT selectionChanged(selectedItem()); } void KeyListView::slotEmitContextMenu(const QPoint &pos) { QTreeWidgetItem *item = itemAt(pos); if (!item || lvi_cast(item)) { Q_EMIT contextMenu(static_cast(item), viewport()->mapToGlobal(pos)); } } // // // KeyListViewItem // // KeyListViewItem::KeyListViewItem(KeyListView *parent, const GpgME::Key &key) : QTreeWidgetItem(parent, RTTI) { Q_ASSERT(parent); setKey(key); } KeyListViewItem::KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key) : QTreeWidgetItem(parent, after, RTTI) { Q_ASSERT(parent); setKey(key); } KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key) : QTreeWidgetItem(parent, RTTI) { Q_ASSERT(parent && parent->listView()); setKey(key); } KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key) : QTreeWidgetItem(parent, after, RTTI) { Q_ASSERT(parent && parent->listView()); setKey(key); } KeyListViewItem::~KeyListViewItem() { // delete the children first... When children are deleted in the // QLVI dtor, they don't have listView() anymore, thus they don't // call deregister( this ), leading to stale entries in the // itemMap... while (QTreeWidgetItem *item = child(0)) { delete item; } // better do this here, too, since deletion is top-down and thus // we're deleted when our parent item is no longer a // KeyListViewItem, but a mere QListViewItem, so our takeItem() // overload is gone by that time... if (KeyListView *lv = listView()) { lv->deregisterItem(this); } } void KeyListViewItem::setKey(const GpgME::Key &key) { KeyListView *lv = listView(); if (lv) { lv->deregisterItem(this); } mKey = key; if (lv) { lv->registerItem(this); } // the ColumnStrategy operations might be very slow, so cache their // result here, where we're non-const :) const KeyListView::ColumnStrategy *cs = lv ? lv->columnStrategy() : nullptr; if (!cs) { return; } const KeyListView::DisplayStrategy *ds = lv->displayStrategy(); const int numCols = lv ? lv->columnCount() : 0; for (int i = 0; i < numCols; ++i) { setText(i, cs->text(key, i)); const auto accessibleText = cs->accessibleText(key, i); if (!accessibleText.isEmpty()) { setData(i, Qt::AccessibleTextRole, accessibleText); } setToolTip(i, cs->toolTip(key, i)); const QIcon icon = cs->icon(key, i); if (!icon.isNull()) { setIcon(i, icon); } if (ds) { setForeground(i, QBrush(ds->keyForeground(key, foreground(i).color()))); setBackground(i, QBrush(ds->keyBackground(key, background(i).color()))); setFont(i, ds->keyFont(key, font(i))); } } } QString KeyListViewItem::toolTip(int col) const { return listView() && listView()->columnStrategy() ? listView()->columnStrategy()->toolTip(key(), col) : QString(); } bool KeyListViewItem::operator<(const QTreeWidgetItem &other) const { if (other.type() != RTTI || !listView() || !listView()->columnStrategy()) { return QTreeWidgetItem::operator<(other); } const auto that = static_cast(&other); return listView()->columnStrategy()->compare(this->key(), that->key(), treeWidget()->sortColumn()) < 0; } void KeyListViewItem::takeItem(QTreeWidgetItem *qlvi) { // qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListViewItem::takeItem(" << qlvi <<" )"; if (auto *item = lvi_cast(qlvi)) { listView()->deregisterItem(item); } takeChild(indexOfChild(qlvi)); } // // // ColumnStrategy // // KeyListView::ColumnStrategy::~ColumnStrategy() { } int KeyListView::ColumnStrategy::compare(const GpgME::Key &key1, const GpgME::Key &key2, const int col) const { return QString::localeAwareCompare(text(key1, col), text(key2, col)); } int KeyListView::ColumnStrategy::width(int col, const QFontMetrics &fm) const { return fm.horizontalAdvance(title(col)) * 2; } QString KeyListView::ColumnStrategy::toolTip(const GpgME::Key &key, int col) const { return text(key, col); } // // // DisplayStrategy // // KeyListView::DisplayStrategy::~DisplayStrategy() { } // font QFont KeyListView::DisplayStrategy::keyFont(const GpgME::Key &, const QFont &font) const { return font; } // foreground QColor KeyListView::DisplayStrategy::keyForeground(const GpgME::Key &, const QColor &fg) const { return fg; } // background QColor KeyListView::DisplayStrategy::keyBackground(const GpgME::Key &, const QColor &bg) const { return bg; } // // // Collection of covariant return reimplementations of QListView(Item) // members: // // KeyListView *KeyListViewItem::listView() const { return static_cast(QTreeWidgetItem::treeWidget()); } KeyListViewItem *KeyListViewItem::nextSibling() const { if (parent()) { const int myIndex = parent()->indexOfChild(const_cast(this)); return static_cast(parent()->child(myIndex + 1)); } const int myIndex = treeWidget()->indexOfTopLevelItem(const_cast(this)); return static_cast(treeWidget()->topLevelItem(myIndex + 1)); } KeyListViewItem *KeyListView::firstChild() const { return static_cast(topLevelItem(0)); } KeyListViewItem *KeyListView::selectedItem() const { QList selection = selectedItems(); if (selection.isEmpty()) { return nullptr; } return selection.first(); } QList KeyListView::selectedItems() const { QList result; const auto selectedItems = QTreeWidget::selectedItems(); for (QTreeWidgetItem *selectedItem : selectedItems) { if (auto *i = lvi_cast(selectedItem)) { result.append(i); } } return result; } bool KeyListView::isMultiSelection() const { return selectionMode() == ExtendedSelection || selectionMode() == MultiSelection; } void KeyListView::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { if (selectedItem()) { slotEmitReturnPressed(selectedItem()); } } QTreeView::keyPressEvent(event); } #include "moc_keylistview.cpp" diff --git a/src/ui/keylistview.h b/src/ui/keylistview.h index 5469672a7..fd5134b0b 100644 --- a/src/ui/keylistview.h +++ b/src/ui/keylistview.h @@ -1,199 +1,199 @@ /* keylistview.h This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" -#include "navigatabletreewidget.h" +#include "treewidget.h" #include #include #include #include class QFont; class QColor; namespace Kleo { // work around moc parser bug... #define TEMPLATE_TYPENAME(T) template TEMPLATE_TYPENAME(T) inline T *lvi_cast(QTreeWidgetItem *item) { return item && (item->type() == T::RTTI) ? static_cast(item) : nullptr; } TEMPLATE_TYPENAME(T) inline const T *lvi_cast(const QTreeWidgetItem *item) { return item && (item->type() == T::RTTI) ? static_cast(item) : nullptr; } #undef TEMPLATE_TYPENAME class KeyListView; class KeyListViewItem : public QTreeWidgetItem { public: KeyListViewItem(KeyListView *parent, const GpgME::Key &key); KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key); KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key); KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key); ~KeyListViewItem() override; void setKey(const GpgME::Key &key); const GpgME::Key &key() const { return mKey; } enum { RTTI = QTreeWidgetItem::UserType + 1 }; // // only boring stuff below: // virtual QString toolTip(int column) const; /*! \reimp for covariant return */ KeyListView *listView() const; /*! \reimp for covariant return */ KeyListViewItem *nextSibling() const; /*! \reimp */ bool operator<(const QTreeWidgetItem &other) const override; /*! \reimp */ void takeItem(QTreeWidgetItem *item); private: GpgME::Key mKey; }; -class KLEO_EXPORT KeyListView : public NavigatableTreeWidget +class KLEO_EXPORT KeyListView : public TreeWidget { Q_OBJECT friend class KeyListViewItem; public: class KLEO_EXPORT ColumnStrategy { public: virtual ~ColumnStrategy(); virtual QString title(int column) const = 0; virtual int width(int column, const QFontMetrics &fm) const; virtual QHeaderView::ResizeMode resizeMode(int) const { return QHeaderView::Interactive; } virtual QString text(const GpgME::Key &key, int column) const = 0; virtual QString accessibleText(const GpgME::Key &key, int column) const = 0; virtual QString toolTip(const GpgME::Key &key, int column) const; virtual QIcon icon(const GpgME::Key &, int) const { return QIcon(); } virtual int compare(const GpgME::Key &key1, const GpgME::Key &key2, const int column) const; }; class KLEO_EXPORT DisplayStrategy { public: virtual ~DisplayStrategy(); // font virtual QFont keyFont(const GpgME::Key &, const QFont &) const; // foreground virtual QColor keyForeground(const GpgME::Key &, const QColor &) const; // background virtual QColor keyBackground(const GpgME::Key &, const QColor &) const; }; explicit KeyListView(const ColumnStrategy *strategy, const DisplayStrategy *display = nullptr, QWidget *parent = nullptr, Qt::WindowFlags f = {}); ~KeyListView() override; const ColumnStrategy *columnStrategy() const { return mColumnStrategy; } const DisplayStrategy *displayStrategy() const { return mDisplayStrategy; } bool hierarchical() const { return mHierarchical; } virtual void setHierarchical(bool hier); void flushKeys() { slotUpdateTimeout(); } bool isMultiSelection() const; KeyListViewItem *itemByFingerprint(const QByteArray &) const; public: using QTreeWidget::selectionChanged; // for below, but moc doesn't like it to be in the Q_SIGNALS: section Q_SIGNALS: void doubleClicked(Kleo::KeyListViewItem *, int); void returnPressed(Kleo::KeyListViewItem *); void selectionChanged(Kleo::KeyListViewItem *); void contextMenu(Kleo::KeyListViewItem *, const QPoint &); protected: void keyPressEvent(QKeyEvent *event) override; public Q_SLOTS: virtual void slotAddKey(const GpgME::Key &key); virtual void slotRefreshKey(const GpgME::Key &key); // // Only boring stuff below: // private Q_SLOTS: void slotEmitDoubleClicked(QTreeWidgetItem *, int); void slotEmitReturnPressed(QTreeWidgetItem *); void slotEmitSelectionChanged(); void slotEmitContextMenu(const QPoint &pos); void slotUpdateTimeout(); public: /*! \reimp for covariant return */ KeyListViewItem *selectedItem() const; /*! \reimp */ QList selectedItems() const; /*! \reimp for covariant return */ KeyListViewItem *firstChild() const; /*! \reimp */ void clear(); /*! \reimp */ void takeItem(QTreeWidgetItem *); private: void doHierarchicalInsert(const GpgME::Key &); void gatherScattered(); void scatterGathered(KeyListViewItem *); void registerItem(KeyListViewItem *); void deregisterItem(const KeyListViewItem *); private: const ColumnStrategy *mColumnStrategy = nullptr; const DisplayStrategy *mDisplayStrategy = nullptr; bool mHierarchical = false; class KeyListViewPrivate; std::unique_ptr const d; }; } diff --git a/src/ui/navigatabletreeview.cpp b/src/ui/navigatabletreeview.cpp deleted file mode 100644 index c3268bcf5..000000000 --- a/src/ui/navigatabletreeview.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - ui/navigatabletreeview.cpp - - This file is part of libkleopatra - SPDX-FileCopyrightText: 2022 g10 Code GmbH - SPDX-FileContributor: Ingo Klöcker - - SPDX-License-Identifier: GPL-2.0-or-later -*/ - -#include - -#include "navigatabletreeview.h" - -using namespace Kleo; - -QModelIndex NavigatableTreeView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) -{ - // make column by column keyboard navigation with Left/Right possible by switching - // the selection behavior to SelectItems before calling the parent class's moveCursor, - // because it ignores MoveLeft/MoveRight if the selection behavior is SelectRows; - // moreover, temporarily disable exanding of items to prevent expanding/collapsing - // on MoveLeft/MoveRight - if ((cursorAction != MoveLeft) && (cursorAction != MoveRight)) { - return QTreeView::moveCursor(cursorAction, modifiers); - } - - const auto savedSelectionBehavior = selectionBehavior(); - setSelectionBehavior(SelectItems); - const auto savedItemsExpandable = itemsExpandable(); - setItemsExpandable(false); - - const auto result = QTreeView::moveCursor(cursorAction, modifiers); - - setItemsExpandable(savedItemsExpandable); - setSelectionBehavior(savedSelectionBehavior); - - return result; -} - -#include "moc_navigatabletreeview.cpp" diff --git a/src/ui/navigatabletreewidget.cpp b/src/ui/navigatabletreewidget.cpp deleted file mode 100644 index b9391abbe..000000000 --- a/src/ui/navigatabletreewidget.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - ui/navigatabletreewidget.cpp - - This file is part of libkleopatra - SPDX-FileCopyrightText: 2022 g10 Code GmbH - SPDX-FileContributor: Ingo Klöcker - - SPDX-License-Identifier: GPL-2.0-or-later -*/ - -#include - -#include "navigatabletreewidget.h" - -using namespace Kleo; - -QModelIndex NavigatableTreeWidget::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) -{ - // make column by column keyboard navigation with Left/Right possible by switching - // the selection behavior to SelectItems before calling the parent class's moveCursor, - // because it ignores MoveLeft/MoveRight if the selection behavior is SelectRows; - // moreover, temporarily disable exanding of items to prevent expanding/collapsing - // on MoveLeft/MoveRight - if ((cursorAction != MoveLeft) && (cursorAction != MoveRight)) { - return QTreeWidget::moveCursor(cursorAction, modifiers); - } - - const auto savedSelectionBehavior = selectionBehavior(); - setSelectionBehavior(SelectItems); - const auto savedItemsExpandable = itemsExpandable(); - setItemsExpandable(false); - - const auto result = QTreeWidget::moveCursor(cursorAction, modifiers); - - setItemsExpandable(savedItemsExpandable); - setSelectionBehavior(savedSelectionBehavior); - - return result; -} - -#include "moc_navigatabletreewidget.cpp" diff --git a/src/ui/treeview.cpp b/src/ui/treeview.cpp new file mode 100644 index 000000000..a12e21337 --- /dev/null +++ b/src/ui/treeview.cpp @@ -0,0 +1,110 @@ +/* + ui/treeview.cpp + + This file is part of libkleopatra + SPDX-FileCopyrightText: 2022 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include "treeview.h" + +#include + +#include +#include +#include + +using namespace Kleo; + +class TreeView::Private +{ +public: + Private() + { + } + QMenu *mHeaderPopup = nullptr; + QList mColumnActions; +}; + +TreeView::TreeView(QWidget *parent) + : QTreeView::QTreeView(parent) + , d{new Private} +{ + header()->installEventFilter(this); +} + +TreeView::~TreeView() = default; + +bool TreeView::eventFilter(QObject *watched, QEvent *event) +{ + Q_UNUSED(watched) + if (event->type() == QEvent::ContextMenu) { + auto e = static_cast(event); + + if (!d->mHeaderPopup) { + d->mHeaderPopup = new QMenu(this); + d->mHeaderPopup->setTitle(i18nc("@title:menu", "View Columns")); + for (int i = 0; i < model()->columnCount(); ++i) { + QAction *tmp = d->mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString()); + tmp->setData(QVariant(i)); + tmp->setCheckable(true); + d->mColumnActions << tmp; + } + + connect(d->mHeaderPopup, &QMenu::triggered, this, [this](QAction *action) { + const int col = action->data().toInt(); + if (action->isChecked()) { + showColumn(col); + } else { + hideColumn(col); + } + + if (action->isChecked()) { + Q_EMIT columnEnabled(col); + } else { + Q_EMIT columnDisabled(col); + } + }); + } + + for (QAction *action : std::as_const(d->mColumnActions)) { + const int column = action->data().toInt(); + action->setChecked(!isColumnHidden(column)); + } + + d->mHeaderPopup->popup(mapToGlobal(e->pos())); + return true; + } + + return false; +} + +QModelIndex TreeView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + // make column by column keyboard navigation with Left/Right possible by switching + // the selection behavior to SelectItems before calling the parent class's moveCursor, + // because it ignores MoveLeft/MoveRight if the selection behavior is SelectRows; + // moreover, temporarily disable exanding of items to prevent expanding/collapsing + // on MoveLeft/MoveRight + if ((cursorAction != MoveLeft) && (cursorAction != MoveRight)) { + return QTreeView::moveCursor(cursorAction, modifiers); + } + + const auto savedSelectionBehavior = selectionBehavior(); + setSelectionBehavior(SelectItems); + const auto savedItemsExpandable = itemsExpandable(); + setItemsExpandable(false); + + const auto result = QTreeView::moveCursor(cursorAction, modifiers); + + setItemsExpandable(savedItemsExpandable); + setSelectionBehavior(savedSelectionBehavior); + + return result; +} + +#include "moc_treeview.cpp" diff --git a/src/ui/navigatabletreeview.h b/src/ui/treeview.h similarity index 68% rename from src/ui/navigatabletreeview.h rename to src/ui/treeview.h index 1b10408ce..0fd42db79 100644 --- a/src/ui/navigatabletreeview.h +++ b/src/ui/treeview.h @@ -1,48 +1,59 @@ /* - ui/navigatabletreeview.h + ui/treeview.h This file is part of libkleopatra SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" #include namespace Kleo { /** - * A tree view that allows accessible column by column keyboard navigation. + * A tree view that allows accessible column by column keyboard navigation + * and that has customizable columns through a context menu in the header. * * Column by column navigation is required to make a tree view accessible. * - * The NavigatableTreeView allows column by column keyboard navigation even if + * The TreeView allows column by column keyboard navigation even if * the selection behavior is set to SelectRows and users can expand/collapse * list items. To achieve this it deactivates the standard behavior of QTreeView * to expand/collapse items if the left/right arrow keys are used. * * Additionally, you may want to disable parent-child navigation in tree views * with left/right arrow keys because this also interferes with column by column * navigation. You can do this by setting * "QTreeView { arrow-keys-navigate-into-children: 0; }" * as application style sheet. * - * \sa NavigatableTreeWidget + * \sa TreeWidget */ -class KLEO_EXPORT NavigatableTreeView : public QTreeView +class KLEO_EXPORT TreeView : public QTreeView { Q_OBJECT public: - using QTreeView::QTreeView; + TreeView(QWidget *parent = nullptr); + ~TreeView() override; + +Q_SIGNALS: + void columnEnabled(int column); + void columnDisabled(int column); protected: + bool eventFilter(QObject *watched, QEvent *event) override; + QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override; -}; +private: + class Private; + const std::unique_ptr d; +}; } diff --git a/src/ui/treewidget.cpp b/src/ui/treewidget.cpp new file mode 100644 index 000000000..9db65c8f6 --- /dev/null +++ b/src/ui/treewidget.cpp @@ -0,0 +1,107 @@ +/* + ui/treewidget.cpp + + This file is part of libkleopatra + SPDX-FileCopyrightText: 2022 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include "treewidget.h" + +#include + +#include +#include +#include + +using namespace Kleo; + +class TreeWidget::Private +{ +public: + QMenu *mHeaderPopup = nullptr; + QList mColumnActions; +}; + +TreeWidget::TreeWidget(QWidget *parent) + : QTreeWidget::QTreeWidget(parent) + , d{new Private} +{ + header()->installEventFilter(this); +} + +TreeWidget::~TreeWidget() = default; + +bool TreeWidget::eventFilter(QObject *watched, QEvent *event) +{ + Q_UNUSED(watched) + if (event->type() == QEvent::ContextMenu) { + auto e = static_cast(event); + + if (!d->mHeaderPopup) { + d->mHeaderPopup = new QMenu(this); + d->mHeaderPopup->setTitle(i18nc("@title:menu", "View Columns")); + for (int i = 0; i < model()->columnCount(); ++i) { + QAction *tmp = d->mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString()); + tmp->setData(QVariant(i)); + tmp->setCheckable(true); + d->mColumnActions << tmp; + } + + connect(d->mHeaderPopup, &QMenu::triggered, this, [this](QAction *action) { + const int col = action->data().toInt(); + if (action->isChecked()) { + showColumn(col); + } else { + hideColumn(col); + } + + if (action->isChecked()) { + Q_EMIT columnEnabled(col); + } else { + Q_EMIT columnDisabled(col); + } + }); + } + + for (QAction *action : std::as_const(d->mColumnActions)) { + const int column = action->data().toInt(); + action->setChecked(!isColumnHidden(column)); + } + + d->mHeaderPopup->popup(mapToGlobal(e->pos())); + return true; + } + + return false; +} + +QModelIndex TreeWidget::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + // make column by column keyboard navigation with Left/Right possible by switching + // the selection behavior to SelectItems before calling the parent class's moveCursor, + // because it ignores MoveLeft/MoveRight if the selection behavior is SelectRows; + // moreover, temporarily disable exanding of items to prevent expanding/collapsing + // on MoveLeft/MoveRight + if ((cursorAction != MoveLeft) && (cursorAction != MoveRight)) { + return QTreeWidget::moveCursor(cursorAction, modifiers); + } + + const auto savedSelectionBehavior = selectionBehavior(); + setSelectionBehavior(SelectItems); + const auto savedItemsExpandable = itemsExpandable(); + setItemsExpandable(false); + + const auto result = QTreeWidget::moveCursor(cursorAction, modifiers); + + setItemsExpandable(savedItemsExpandable); + setSelectionBehavior(savedSelectionBehavior); + + return result; +} + +#include "moc_treewidget.cpp" diff --git a/src/ui/navigatabletreewidget.h b/src/ui/treewidget.h similarity index 50% rename from src/ui/navigatabletreewidget.h rename to src/ui/treewidget.h index a4eacf1e7..6dd92bf6f 100644 --- a/src/ui/navigatabletreewidget.h +++ b/src/ui/treewidget.h @@ -1,37 +1,49 @@ /* - ui/navigatabletreewidget.h + ui/treewidget.h This file is part of libkleopatra SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "kleo_export.h" #include namespace Kleo { /** - * A tree widget that allows accessible column by column keyboard navigation. + * A tree widget that allows accessible column by column keyboard navigation + * and that has customizable columns through a context menu in the header. * - * This is the QTreeWidget-derived variant of NavigatableTreeView. + * This is the QTreeWidget-derived variant of TreeView. * - * \sa NavigatableTreeView + * \sa TreeView */ -class KLEO_EXPORT NavigatableTreeWidget : public QTreeWidget +class KLEO_EXPORT TreeWidget : public QTreeWidget { Q_OBJECT public: - using QTreeWidget::QTreeWidget; + TreeWidget(QWidget *parent = nullptr); + ~TreeWidget() override; + +Q_SIGNALS: + void columnEnabled(int column); + void columnDisabled(int column); protected: + bool eventFilter(QObject *watched, QEvent *event) override; + QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override; + +private: + class Private; + const std::unique_ptr d; }; }