diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5db03cf80..cb05f407d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,614 +1,617 @@ # SPDX-FileCopyrightText: none # SPDX-License-Identifier: BSD-3-Clause add_subdirectory(icons) add_subdirectory(mimetypes) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if (NOT DISABLE_KWATCHGNUPG) add_subdirectory(kwatchgnupg) endif() add_subdirectory(libkleopatraclient) add_subdirectory(conf) add_subdirectory(kconf_update) if(WIN32) set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_win.cpp) set(_kleopatra_extra_SRCS selftest/registrycheck.cpp utils/gnupg-registry.c utils/userinfo_win.cpp utils/windowsprocessdevice.cpp ) else() set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_unix.cpp) set(_kleopatra_extra_SRCS) endif() set(_kleopatra_uiserver_SRCS ${_kleopatra_extra_uiserver_SRCS} selftest/uiservercheck.cpp uiserver/assuanserverconnection.cpp uiserver/createchecksumscommand.cpp uiserver/decryptverifycommandemailbase.cpp uiserver/decryptverifycommandfilesbase.cpp uiserver/echocommand.cpp uiserver/encryptcommand.cpp uiserver/importfilescommand.cpp uiserver/prepencryptcommand.cpp uiserver/prepsigncommand.cpp uiserver/selectcertificatecommand.cpp uiserver/sessiondata.cpp uiserver/signcommand.cpp uiserver/signencryptfilescommand.cpp uiserver/uiserver.cpp uiserver/verifychecksumscommand.cpp ) if(ASSUAN2_FOUND) include_directories(${ASSUAN2_INCLUDES}) set(_kleopatra_uiserver_extra_libs ${ASSUAN2_LIBRARIES}) else() include_directories(${ASSUAN_INCLUDES}) if(WIN32) set(_kleopatra_uiserver_extra_libs ${ASSUAN_VANILLA_LIBRARIES}) else() set(_kleopatra_uiserver_extra_libs ${ASSUAN_PTHREAD_LIBRARIES}) endif() endif() if(HAVE_GPG_ERR_SOURCE_KLEO) add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) else() add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) endif() if(KF5IdentityManagement_FOUND AND KF5MailTransport_FOUND AND KF5MailTransportAkonadi_FOUND) set(_kleopatra_mail_libs KF5::IdentityManagement # Export OpenPGP keys using WKS KF5::MailTransport KF5::MailTransportAkonadi ) add_definitions(-DMAILAKONADI_ENABLED) endif() ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui) set(_kleopatra_SRCS ${_kleopatra_extra_SRCS} + accessibility/accessiblelink.cpp + accessibility/accessiblelink_p.h accessibility/accessiblerichtextlabel.cpp accessibility/accessiblerichtextlabel_p.h accessibility/accessiblewidgetfactory.cpp accessibility/accessiblewidgetfactory.h commands/adduseridcommand.cpp commands/adduseridcommand.h commands/authenticatepivcardapplicationcommand.cpp commands/authenticatepivcardapplicationcommand.h commands/cardcommand.cpp commands/cardcommand.h commands/certificatetopivcardcommand.cpp commands/certificatetopivcardcommand.h commands/certifycertificatecommand.cpp commands/certifycertificatecommand.h commands/changeexpirycommand.cpp commands/changeexpirycommand.h commands/changeownertrustcommand.cpp commands/changeownertrustcommand.h commands/changepassphrasecommand.cpp commands/changepassphrasecommand.h commands/changepincommand.cpp commands/changepincommand.h commands/changeroottrustcommand.cpp commands/changeroottrustcommand.h commands/checksumcreatefilescommand.cpp commands/checksumcreatefilescommand.h commands/checksumverifyfilescommand.cpp commands/checksumverifyfilescommand.h commands/clearcrlcachecommand.cpp commands/clearcrlcachecommand.h commands/command.cpp commands/command.h commands/createcsrforcardkeycommand.cpp commands/createcsrforcardkeycommand.h commands/createopenpgpkeyfromcardkeyscommand.cpp commands/createopenpgpkeyfromcardkeyscommand.h commands/decryptverifyclipboardcommand.cpp commands/decryptverifyclipboardcommand.h commands/decryptverifyfilescommand.cpp commands/decryptverifyfilescommand.h commands/deletecertificatescommand.cpp commands/deletecertificatescommand.h commands/detailscommand.cpp commands/detailscommand.h commands/dumpcertificatecommand.cpp commands/dumpcertificatecommand.h commands/dumpcrlcachecommand.cpp commands/dumpcrlcachecommand.h commands/encryptclipboardcommand.cpp commands/encryptclipboardcommand.h commands/exportcertificatecommand.cpp commands/exportcertificatecommand.h commands/exportgroupscommand.cpp commands/exportgroupscommand.h commands/exportopenpgpcertstoservercommand.cpp commands/exportopenpgpcertstoservercommand.h commands/exportopenpgpcerttoprovidercommand.cpp commands/exportopenpgpcerttoprovidercommand.h commands/exportpaperkeycommand.cpp commands/exportpaperkeycommand.h commands/exportsecretkeycommand.cpp commands/exportsecretkeycommand.h commands/exportsecretkeycommand_old.cpp commands/exportsecretkeycommand_old.h commands/exportsecretsubkeycommand.cpp commands/exportsecretsubkeycommand.h commands/genrevokecommand.cpp commands/genrevokecommand.h commands/gnupgprocesscommand.cpp commands/gnupgprocesscommand.h commands/importcertificatefromclipboardcommand.cpp commands/importcertificatefromclipboardcommand.h commands/importcertificatefromdatacommand.cpp commands/importcertificatefromdatacommand.h commands/importcertificatefromfilecommand.cpp commands/importcertificatefromfilecommand.h commands/importcertificatefromkeyservercommand.cpp commands/importcertificatefromkeyservercommand.h commands/importcertificatefrompivcardcommand.cpp commands/importcertificatefrompivcardcommand.h commands/importcertificatescommand.cpp commands/importcertificatescommand.h commands/importcrlcommand.cpp commands/importcrlcommand.h commands/importpaperkeycommand.cpp commands/importpaperkeycommand.h commands/keytocardcommand.cpp commands/keytocardcommand.h commands/learncardkeyscommand.cpp commands/learncardkeyscommand.h commands/lookupcertificatescommand.cpp commands/lookupcertificatescommand.h commands/newcertificatecommand.cpp commands/newcertificatecommand.h commands/newopenpgpcertificatecommand.cpp commands/newopenpgpcertificatecommand.h commands/pivgeneratecardkeycommand.cpp commands/pivgeneratecardkeycommand.h commands/refreshcertificatecommand.cpp commands/refreshcertificatecommand.h commands/refreshopenpgpcertscommand.cpp commands/refreshopenpgpcertscommand.h commands/refreshx509certscommand.cpp commands/refreshx509certscommand.h commands/reloadkeyscommand.cpp commands/reloadkeyscommand.h commands/revokecertificationcommand.cpp commands/revokecertificationcommand.h commands/revokekeycommand.cpp commands/revokekeycommand.h commands/revokeuseridcommand.cpp commands/revokeuseridcommand.h commands/selftestcommand.cpp commands/selftestcommand.h commands/setinitialpincommand.cpp commands/setinitialpincommand.h commands/setpivcardapplicationadministrationkeycommand.cpp commands/setpivcardapplicationadministrationkeycommand.h commands/signclipboardcommand.cpp commands/signclipboardcommand.h commands/signencryptfilescommand.cpp commands/signencryptfilescommand.h commands/signencryptfoldercommand.cpp commands/signencryptfoldercommand.h conf/configuredialog.cpp conf/configuredialog.h conf/groupsconfigdialog.cpp conf/groupsconfigdialog.h conf/groupsconfigpage.cpp conf/groupsconfigpage.h conf/groupsconfigwidget.cpp conf/groupsconfigwidget.h crypto/autodecryptverifyfilescontroller.cpp crypto/autodecryptverifyfilescontroller.h crypto/certificateresolver.cpp crypto/certificateresolver.h crypto/checksumsutils_p.cpp crypto/checksumsutils_p.h crypto/controller.cpp crypto/controller.h crypto/createchecksumscontroller.cpp crypto/createchecksumscontroller.h crypto/decryptverifyemailcontroller.cpp crypto/decryptverifyemailcontroller.h crypto/decryptverifyfilescontroller.cpp crypto/decryptverifyfilescontroller.h crypto/decryptverifytask.cpp crypto/decryptverifytask.h crypto/encryptemailcontroller.cpp crypto/encryptemailcontroller.h crypto/encryptemailtask.cpp crypto/encryptemailtask.h crypto/gui/certificatelineedit.cpp crypto/gui/certificatelineedit.h crypto/gui/certificateselectionline.cpp crypto/gui/certificateselectionline.h crypto/gui/decryptverifyfilesdialog.cpp crypto/gui/decryptverifyfilesdialog.h crypto/gui/decryptverifyfileswizard.cpp crypto/gui/decryptverifyfileswizard.h crypto/gui/decryptverifyoperationwidget.cpp crypto/gui/decryptverifyoperationwidget.h crypto/gui/encryptemailwizard.cpp crypto/gui/encryptemailwizard.h crypto/gui/newresultpage.cpp crypto/gui/newresultpage.h crypto/gui/objectspage.cpp crypto/gui/objectspage.h crypto/gui/resolverecipientspage.cpp crypto/gui/resolverecipientspage.h crypto/gui/resultitemwidget.cpp crypto/gui/resultitemwidget.h crypto/gui/resultlistwidget.cpp crypto/gui/resultlistwidget.h crypto/gui/resultpage.cpp crypto/gui/resultpage.h crypto/gui/signemailwizard.cpp crypto/gui/signemailwizard.h crypto/gui/signencryptemailconflictdialog.cpp crypto/gui/signencryptemailconflictdialog.h crypto/gui/signencryptfileswizard.cpp crypto/gui/signencryptfileswizard.h crypto/gui/signencryptwidget.cpp crypto/gui/signencryptwidget.h crypto/gui/signencryptwizard.cpp crypto/gui/signencryptwizard.h crypto/gui/signerresolvepage.cpp crypto/gui/signerresolvepage.h crypto/gui/signingcertificateselectiondialog.cpp crypto/gui/signingcertificateselectiondialog.h crypto/gui/signingcertificateselectionwidget.cpp crypto/gui/signingcertificateselectionwidget.h crypto/gui/unknownrecipientwidget.cpp crypto/gui/unknownrecipientwidget.h crypto/gui/verifychecksumsdialog.cpp crypto/gui/verifychecksumsdialog.h crypto/gui/wizard.cpp crypto/gui/wizard.h crypto/gui/wizardpage.cpp crypto/gui/wizardpage.h crypto/newsignencryptemailcontroller.cpp crypto/newsignencryptemailcontroller.h crypto/recipient.cpp crypto/recipient.h crypto/sender.cpp crypto/sender.h crypto/signemailcontroller.cpp crypto/signemailcontroller.h crypto/signemailtask.cpp crypto/signemailtask.h crypto/signencryptfilescontroller.cpp crypto/signencryptfilescontroller.h crypto/signencrypttask.cpp crypto/signencrypttask.h crypto/task.cpp crypto/task.h crypto/taskcollection.cpp crypto/taskcollection.h crypto/verifychecksumscontroller.cpp crypto/verifychecksumscontroller.h dialogs/adduseriddialog.cpp dialogs/adduseriddialog.h dialogs/certificatedetailsdialog.cpp dialogs/certificatedetailsdialog.h dialogs/certificatedetailsinputwidget.cpp dialogs/certificatedetailsinputwidget.h dialogs/certificatedetailswidget.cpp dialogs/certificatedetailswidget.h dialogs/certificateselectiondialog.cpp dialogs/certificateselectiondialog.h dialogs/certifycertificatedialog.cpp dialogs/certifycertificatedialog.h dialogs/certifywidget.cpp dialogs/certifywidget.h dialogs/choosecertificateprotocoldialog.cpp dialogs/choosecertificateprotocoldialog.h dialogs/createcsrforcardkeydialog.cpp dialogs/createcsrforcardkeydialog.h dialogs/deletecertificatesdialog.cpp dialogs/deletecertificatesdialog.h dialogs/editgroupdialog.cpp dialogs/editgroupdialog.h dialogs/expirydialog.cpp dialogs/expirydialog.h dialogs/exportdialog.cpp dialogs/exportdialog.h dialogs/gencardkeydialog.cpp dialogs/gencardkeydialog.h dialogs/groupdetailsdialog.cpp dialogs/groupdetailsdialog.h dialogs/lookupcertificatesdialog.cpp dialogs/lookupcertificatesdialog.h dialogs/nameandemailwidget.cpp dialogs/nameandemailwidget.h dialogs/newopenpgpcertificatedetailsdialog.cpp dialogs/newopenpgpcertificatedetailsdialog.h dialogs/newopenpgpcertificateresultdialog.cpp dialogs/newopenpgpcertificateresultdialog.h dialogs/ownertrustdialog.cpp dialogs/ownertrustdialog.h dialogs/pivcardapplicationadministrationkeyinputdialog.cpp dialogs/pivcardapplicationadministrationkeyinputdialog.h dialogs/revokecertificationdialog.cpp dialogs/revokecertificationdialog.h dialogs/revokecertificationwidget.cpp dialogs/revokecertificationwidget.h dialogs/revokekeydialog.cpp dialogs/revokekeydialog.h dialogs/selftestdialog.cpp dialogs/selftestdialog.h dialogs/setinitialpindialog.cpp dialogs/setinitialpindialog.h dialogs/subkeyswidget.cpp dialogs/subkeyswidget.h dialogs/trustchainwidget.cpp dialogs/trustchainwidget.h dialogs/updatenotification.cpp dialogs/updatenotification.h dialogs/weboftrustdialog.cpp dialogs/weboftrustdialog.h dialogs/weboftrustwidget.cpp dialogs/weboftrustwidget.h + interfaces/anchorprovider.h interfaces/focusfirstchild.h newcertificatewizard/advancedsettingsdialog.cpp newcertificatewizard/advancedsettingsdialog_p.h newcertificatewizard/enterdetailspage.cpp newcertificatewizard/enterdetailspage_p.h newcertificatewizard/keyalgo.cpp newcertificatewizard/keyalgo_p.h newcertificatewizard/keycreationpage.cpp newcertificatewizard/keycreationpage_p.h newcertificatewizard/listwidget.cpp newcertificatewizard/listwidget.h newcertificatewizard/newcertificatewizard.cpp newcertificatewizard/newcertificatewizard.h newcertificatewizard/resultpage.cpp newcertificatewizard/resultpage_p.h newcertificatewizard/wizardpage.cpp newcertificatewizard/wizardpage_p.h selftest/compliancecheck.cpp selftest/compliancecheck.h selftest/enginecheck.cpp selftest/enginecheck.h selftest/gpgagentcheck.cpp selftest/gpgagentcheck.h selftest/gpgconfcheck.cpp selftest/gpgconfcheck.h selftest/libkleopatrarccheck.cpp selftest/libkleopatrarccheck.h selftest/selftest.cpp selftest/selftest.h smartcard/card.cpp smartcard/card.h smartcard/deviceinfowatcher.cpp smartcard/deviceinfowatcher.h smartcard/keypairinfo.cpp smartcard/keypairinfo.h smartcard/netkeycard.cpp smartcard/netkeycard.h smartcard/openpgpcard.cpp smartcard/openpgpcard.h smartcard/p15card.cpp smartcard/p15card.h smartcard/pivcard.cpp smartcard/pivcard.h smartcard/readerstatus.cpp smartcard/readerstatus.h smartcard/utils.cpp smartcard/utils.h utils/accessibility.cpp utils/accessibility.h utils/action_data.cpp utils/action_data.h utils/archivedefinition.cpp utils/archivedefinition.h utils/auditlog.cpp utils/auditlog.h utils/clipboardmenu.cpp utils/clipboardmenu.h utils/debug-helpers.cpp utils/debug-helpers.h utils/dragqueen.cpp utils/dragqueen.h utils/email.cpp utils/email.h utils/emptypassphraseprovider.cpp utils/emptypassphraseprovider.h utils/filedialog.cpp utils/filedialog.h utils/gui-helper.cpp utils/gui-helper.h utils/headerview.cpp utils/headerview.h utils/input.cpp utils/input.h utils/iodevicelogger.cpp utils/iodevicelogger.h utils/kdpipeiodevice.cpp utils/kdpipeiodevice.h utils/keyparameters.cpp utils/keyparameters.h utils/keys.cpp utils/keys.h utils/kuniqueservice.cpp utils/kuniqueservice.h utils/log.cpp utils/log.h utils/multivalidator.cpp utils/multivalidator.h utils/output.cpp utils/output.h utils/path-helper.cpp utils/path-helper.h utils/scrollarea.cpp utils/scrollarea.h utils/systemtrayicon.cpp utils/systemtrayicon.h utils/tags.cpp utils/tags.h utils/types.cpp utils/types.h utils/userinfo.cpp utils/userinfo.h utils/validation.cpp utils/validation.h utils/writecertassuantransaction.cpp utils/writecertassuantransaction.h utils/wsastarter.cpp utils/wsastarter.h view/errorlabel.cpp view/errorlabel.h view/formtextinput.cpp view/formtextinput.h view/htmllabel.cpp view/htmllabel.h view/keycacheoverlay.cpp view/keycacheoverlay.h view/keylistcontroller.cpp view/keylistcontroller.h view/keytreeview.cpp view/keytreeview.h view/netkeywidget.cpp view/netkeywidget.h view/nullpinwidget.cpp view/nullpinwidget.h view/openpgpkeycardwidget.cpp view/openpgpkeycardwidget.h view/p15cardwidget.cpp view/p15cardwidget.h view/padwidget.cpp view/padwidget.h view/pgpcardwidget.cpp view/pgpcardwidget.h view/pivcardwidget.cpp view/pivcardwidget.h view/searchbar.cpp view/searchbar.h view/smartcardwidget.cpp view/smartcardwidget.h view/tabwidget.cpp view/tabwidget.h view/urllabel.cpp view/urllabel.h view/waitwidget.cpp view/waitwidget.h view/welcomewidget.cpp view/welcomewidget.h aboutdata.cpp aboutdata.h kleopatra.qrc kleopatraapplication.cpp kleopatraapplication.h main.cpp mainwindow.cpp mainwindow.h systrayicon.cpp systrayicon.h ) if(WIN32) configure_file (versioninfo.rc.in versioninfo.rc) set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc ${_kleopatra_SRCS}) endif() set (_kleopatra_SRCS conf/kleopageconfigdialog.cpp ${_kleopatra_SRCS}) ecm_qt_declare_logging_category(_kleopatra_SRCS HEADER kleopatra_debug.h IDENTIFIER KLEOPATRA_LOG CATEGORY_NAME org.kde.pim.kleopatra DESCRIPTION "kleopatra (kleopatra)" OLD_CATEGORY_NAMES log_kleopatra EXPORT KLEOPATRA ) if(KLEO_MODEL_TEST) add_definitions(-DKLEO_MODEL_TEST) set(_kleopatra_SRCS ${_kleopatra_SRCS} models/modeltest.cpp) endif() ki18n_wrap_ui(_kleopatra_SRCS dialogs/ownertrustdialog.ui dialogs/selectchecklevelwidget.ui dialogs/selftestdialog.ui dialogs/setinitialpindialog.ui dialogs/subkeyswidget.ui dialogs/trustchainwidget.ui newcertificatewizard/listwidget.ui ) kconfig_add_kcfg_files(_kleopatra_SRCS kcfg/emailoperationspreferences.kcfgc kcfg/fileoperationspreferences.kcfgc kcfg/settings.kcfgc kcfg/smimevalidationpreferences.kcfgc kcfg/tagspreferences.kcfgc kcfg/tooltippreferences.kcfgc ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kleopatra.png") ecm_add_app_icon(_kleopatra_SRCS ICONS ${ICONS_SRCS}) add_executable(kleopatra_bin ${_kleopatra_SRCS} ${_kleopatra_uiserver_SRCS}) # For the ConfigureDialog & KCMs target_link_libraries(kleopatra_bin kcm_kleopatra_static) #if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) # set_target_properties(kleopatra_bin PROPERTIES UNITY_BUILD ON) #endif() set_target_properties(kleopatra_bin PROPERTIES OUTPUT_NAME kleopatra) if (WIN32) set(_kleopatra_platform_libs "secur32") endif () target_link_libraries(kleopatra_bin Gpgmepp QGpgme ${_kleopatra_extra_libs} KF5::Libkleo KF5::Mime KF5::I18n KF5::XmlGui KF5::IconThemes KF5::WindowSystem KF5::CoreAddons KF5::ItemModels KF5::Crash ${_kleopatra_mail_libs} Qt${QT_MAJOR_VERSION}::Network Qt${QT_MAJOR_VERSION}::PrintSupport # Printing secret keys ${_kleopatra_uiserver_extra_libs} ${_kleopatra_dbusaddons_libs} kleopatraclientcore ${_kleopatra_platform_libs} ) install(TARGETS kleopatra_bin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install( PROGRAMS data/org.kde.kleopatra.desktop data/kleopatra_import.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install(FILES data/org.kde.kleopatra.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install( PROGRAMS data/kleopatra_signencryptfiles.desktop data/kleopatra_signencryptfolders.desktop data/kleopatra_decryptverifyfiles.desktop data/kleopatra_decryptverifyfolders.desktop DESTINATION ${KDE_INSTALL_DATADIR}/kio/servicemenus ) diff --git a/src/accessibility/accessiblelink.cpp b/src/accessibility/accessiblelink.cpp new file mode 100644 index 000000000..1cfde04e9 --- /dev/null +++ b/src/accessibility/accessiblelink.cpp @@ -0,0 +1,124 @@ +/* + accessibility/accessiblelink.cpp + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2022 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include "accessiblelink_p.h" + +#include + +#include + +using namespace Kleo; + +AccessibleLink::AccessibleLink(QWidget *label, int index) + : mLabel{label} + , mIndex{index} +{ +} + +AccessibleLink::~AccessibleLink() = default; + +bool AccessibleLink::isValid() const +{ + return mLabel; +} + +QObject *AccessibleLink::object() const +{ + return nullptr; +} + +QWindow *AccessibleLink::window() const +{ + if (auto p = parent()) { + return p->window(); + } + return nullptr; +} + +QAccessibleInterface *AccessibleLink::childAt(int, int) const +{ + return nullptr; +} + +QAccessibleInterface *AccessibleLink::parent() const +{ + return QAccessible::queryAccessibleInterface(mLabel); +} + +QAccessibleInterface *AccessibleLink::child(int) const +{ + return nullptr; +} + +int AccessibleLink::childCount() const +{ + return 0; +} + +int AccessibleLink::indexOfChild(const QAccessibleInterface *) const +{ + return -1; +} + +QString AccessibleLink::text(QAccessible::Text t) const +{ + QString str; + switch (t) { + case QAccessible::Name: + if (auto ap = anchorProvider()) { + str = ap->anchorText(mIndex); + } + break; + default: + break; + } + return str; +} + +void AccessibleLink::setText(QAccessible::Text /*t*/, const QString & /*text */) +{ +} + +QRect AccessibleLink::rect() const +{ + if (auto p = parent()) { + return p->rect(); + } + return {}; +} + +QAccessible::Role AccessibleLink::role() const +{ + return QAccessible::Link; +} + +QAccessible::State AccessibleLink::state() const +{ + QAccessible::State s; + if (auto p = parent()) { + s = p->state(); + } + if (auto ap = anchorProvider()) { + s.focused = ap->selectedAnchor() == mIndex; + } + return s; +} + +int AccessibleLink::index() const +{ + return mIndex; +} + +AnchorProvider *AccessibleLink::anchorProvider() const +{ + return dynamic_cast(mLabel.data()); +} diff --git a/src/accessibility/accessiblelink_p.h b/src/accessibility/accessiblelink_p.h new file mode 100644 index 000000000..94d6b5942 --- /dev/null +++ b/src/accessibility/accessiblelink_p.h @@ -0,0 +1,54 @@ +/* + accessibility/accessiblelink_p.h + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2022 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include + +class QWidget; + +namespace Kleo +{ +class AnchorProvider; + +class AccessibleLink: public QAccessibleInterface +{ +public: + AccessibleLink(QWidget *label, int index); + ~AccessibleLink() override; + + bool isValid() const override; + QObject *object() const override; + QWindow *window() const override; + + QAccessibleInterface *childAt(int x, int y) const override; + + QAccessibleInterface *parent() const override; + QAccessibleInterface *child(int index) const override; + int childCount() const override; + int indexOfChild(const QAccessibleInterface * child) const override; + + QString text(QAccessible::Text t) const override; + void setText(QAccessible::Text t, const QString & text) override; + QRect rect() const override; + QAccessible::Role role() const override; + QAccessible::State state() const override; + + int index() const; + +private: + AnchorProvider *anchorProvider() const; + + QPointer mLabel; + int mIndex; +}; + +} diff --git a/src/accessibility/accessiblerichtextlabel.cpp b/src/accessibility/accessiblerichtextlabel.cpp index bc84374c8..27a518d31 100644 --- a/src/accessibility/accessiblerichtextlabel.cpp +++ b/src/accessibility/accessiblerichtextlabel.cpp @@ -1,165 +1,252 @@ /* accessibility/accessiblerichtextlabel.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "accessiblerichtextlabel_p.h" +#include "accessiblelink_p.h" + +#include + #include #include using namespace Kleo; +struct AccessibleRichTextLabel::ChildData { + QAccessible::Id id = 0; +}; + AccessibleRichTextLabel::AccessibleRichTextLabel(QWidget *w) : QAccessibleWidget{w, QAccessible::StaticText} { Q_ASSERT(qobject_cast(w)); } +AccessibleRichTextLabel::~AccessibleRichTextLabel() +{ + clearChildCache(); +} + void *AccessibleRichTextLabel::interface_cast(QAccessible::InterfaceType t) { - if (t == QAccessible::TextInterface) + if (t == QAccessible::TextInterface) { return static_cast(this); + } return QAccessibleWidget::interface_cast(t); } QAccessible::State AccessibleRichTextLabel::state() const { QAccessible::State state = QAccessibleWidget::state(); state.readOnly = true; state.selectableText = true; return state; } QString AccessibleRichTextLabel::text(QAccessible::Text t) const { QString str; switch (t) { case QAccessible::Name: str = widget()->accessibleName(); if (str.isEmpty()) { str = displayText(); } break; default: break; } - if (str.isEmpty()) + if (str.isEmpty()) { str = QAccessibleWidget::text(t); + } return str; } +QAccessibleInterface *AccessibleRichTextLabel::focusChild() const +{ + if (const auto *const ap = anchorProvider()) { + const int childIndex = ap->selectedAnchor(); + if (childIndex >= 0) { + return child(childIndex); + } + } + return QAccessibleWidget::focusChild(); +} + +QAccessibleInterface *AccessibleRichTextLabel::child(int index) const +{ + const auto *const ap = anchorProvider(); + if (ap && index >= 0 && index < ap->numberOfAnchors()) { + auto &childData = childCache()[index]; + if (childData.id != 0) { + return QAccessible::accessibleInterface(childData.id); + } + + QAccessibleInterface *iface = new AccessibleLink{widget(), index}; + childData.id = QAccessible::registerAccessibleInterface(iface); + return iface; + } + return nullptr; +} + +int AccessibleRichTextLabel::childCount() const +{ + if (const auto *const ap = anchorProvider()) { + return ap->numberOfAnchors(); + } + return 0; +} + +int AccessibleRichTextLabel::indexOfChild(const QAccessibleInterface *child) const +{ + if ((child->role() == QAccessible::Link) && (child->parent() == this)) { + return static_cast(child)->index(); + } + return -1; +} + void AccessibleRichTextLabel::selection(int selectionIndex, int *startOffset, int *endOffset) const { *startOffset = *endOffset = 0; if (selectionIndex != 0) return; *startOffset = label()->selectionStart(); *endOffset = *startOffset + label()->selectedText().size(); } int AccessibleRichTextLabel::selectionCount() const { return label()->hasSelectedText() ? 1 : 0; } void AccessibleRichTextLabel::addSelection(int startOffset, int endOffset) { setSelection(0, startOffset, endOffset); } void AccessibleRichTextLabel::removeSelection(int selectionIndex) { if (selectionIndex != 0) return; label()->setSelection(-1, -1); } void AccessibleRichTextLabel::setSelection(int selectionIndex, int startOffset, int endOffset) { if (selectionIndex != 0) return; label()->setSelection(startOffset, endOffset - startOffset); } int AccessibleRichTextLabel::cursorPosition() const { return label()->hasSelectedText() ? label()->selectionStart() + label()->selectedText().size() : 0; } void AccessibleRichTextLabel::setCursorPosition(int position) { Q_UNUSED(position) } QString AccessibleRichTextLabel::text(int startOffset, int endOffset) const { if (startOffset > endOffset) return {}; // most likely the client is asking for the selected text, so return it // instead of a slice of displayText() if the offsets match the selection if (startOffset == label()->selectionStart() && endOffset == startOffset + label()->selectedText().size()) { return label()->selectedText(); } return displayText().mid(startOffset, endOffset - startOffset); } int AccessibleRichTextLabel::characterCount() const { return displayText().size(); } QRect AccessibleRichTextLabel::characterRect(int offset) const { Q_UNUSED(offset) return {}; } int AccessibleRichTextLabel::offsetAtPoint(const QPoint &point) const { Q_UNUSED(point) return -1; } QString AccessibleRichTextLabel::attributes(int offset, int *startOffset, int *endOffset) const { *startOffset = *endOffset = offset; return {}; } void AccessibleRichTextLabel::scrollToSubstring(int startIndex, int endIndex) { Q_UNUSED(startIndex) Q_UNUSED(endIndex) } QLabel *AccessibleRichTextLabel::label() const { return qobject_cast(object()); } +AnchorProvider *AccessibleRichTextLabel::anchorProvider() const +{ + return dynamic_cast(object()); +} + QString AccessibleRichTextLabel::displayText() const { // calculate an approximation of the displayed text without using private // information of QLabel QString str = label()->text(); if (label()->textFormat() == Qt::RichText || (label()->textFormat() == Qt::AutoText && Qt::mightBeRichText(str))) { QTextDocument doc; doc.setHtml(str); str = doc.toPlainText(); } return str; } + +std::vector &AccessibleRichTextLabel::childCache() const +{ + const auto *const ap = anchorProvider(); + if (!ap || static_cast(mChildCache.size()) == ap->numberOfAnchors()) { + return mChildCache; + } + + clearChildCache(); + // fill the cache with default-initialized child data + mChildCache.resize(ap->numberOfAnchors()); + + return mChildCache; +} + +void AccessibleRichTextLabel::clearChildCache() const +{ + std::for_each(std::cbegin(mChildCache), std::cend(mChildCache), [](const auto &child) { + if (child.id != 0) { + QAccessible::deleteAccessibleInterface(child.id); + } + }); + mChildCache.clear(); +} diff --git a/src/accessibility/accessiblerichtextlabel_p.h b/src/accessibility/accessiblerichtextlabel_p.h index 2ab7d64bc..b313c224c 100644 --- a/src/accessibility/accessiblerichtextlabel_p.h +++ b/src/accessibility/accessiblerichtextlabel_p.h @@ -1,57 +1,75 @@ /* accessibility/accessiblerichtextlabel_p.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include class QLabel; namespace Kleo { +class AnchorProvider; class AccessibleRichTextLabel : public QAccessibleWidget, public QAccessibleTextInterface { public: explicit AccessibleRichTextLabel(QWidget *o); + ~AccessibleRichTextLabel() override; void *interface_cast(QAccessible::InterfaceType t) override; QAccessible::State state() const override; QString text(QAccessible::Text t) const override; + // relations + QAccessibleInterface *focusChild() const override; + + // navigation, hierarchy + QAccessibleInterface *child(int index) const override; + int childCount() const override; + int indexOfChild(const QAccessibleInterface *child) const override; + // QAccessibleTextInterface // selection void selection(int selectionIndex, int *startOffset, int *endOffset) const override; int selectionCount() const override; void addSelection(int startOffset, int endOffset) override; void removeSelection(int selectionIndex) override; void setSelection(int selectionIndex, int startOffset, int endOffset) override; // cursor int cursorPosition() const override; void setCursorPosition(int position) override; // text QString text(int startOffset, int endOffset) const override; int characterCount() const override; // character <-> geometry QRect characterRect(int offset) const override; int offsetAtPoint(const QPoint &point) const override; void scrollToSubstring(int startIndex, int endIndex) override; QString attributes(int offset, int *startOffset, int *endOffset) const override; private: + struct ChildData; + QLabel *label() const; + AnchorProvider *anchorProvider() const; QString displayText() const; + + std::vector &childCache() const; + void clearChildCache() const; + + mutable std::vector mChildCache; }; } diff --git a/src/interfaces/anchorprovider.h b/src/interfaces/anchorprovider.h new file mode 100644 index 000000000..913a41c20 --- /dev/null +++ b/src/interfaces/anchorprovider.h @@ -0,0 +1,29 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + interfaces/anchorprovider.h + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2022 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include + +namespace Kleo +{ + +class AnchorProvider +{ +public: + virtual ~AnchorProvider() = default; + + virtual int numberOfAnchors() const = 0; + virtual QString anchorText(int index) const = 0; + virtual QString anchorHref(int index) const = 0; + virtual int selectedAnchor() const = 0; +}; + +} diff --git a/src/view/htmllabel.cpp b/src/view/htmllabel.cpp index 5d7a5f702..49fffe624 100644 --- a/src/view/htmllabel.cpp +++ b/src/view/htmllabel.cpp @@ -1,101 +1,223 @@ /* view/htmllabel.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "htmllabel.h" #include "utils/accessibility.h" #include +#include +#include +#include using namespace Kleo; +namespace +{ +struct AnchorData { + int start; + int end; + QString text; + QString href; +}; +} + class HtmlLabel::Private { HtmlLabel *q; public: Private(HtmlLabel *q) : q{q} {} void updateText(const QString &newText = {}); + std::vector &anchors(); + int anchorIndex(int start); + void invalidateAnchorCache(); + + bool mAnchorsValid = false; + std::vector mAnchors; QColor linkColor; }; void HtmlLabel::Private::updateText(const QString &newText) { static const QString styleTemplate{QLatin1String{""}}; if (newText.isEmpty() && q->text().isEmpty()) { return; } const auto styleTag = styleTemplate.arg(linkColor.isValid() ? linkColor.name() : q->palette().link().color().name()); if (newText.isEmpty()) { q->setText(styleTag + q->text().mid(styleTag.size())); } else { q->setText(styleTag + newText); } + invalidateAnchorCache(); +} + +std::vector &HtmlLabel::Private::anchors() +{ + if (mAnchorsValid) { + return mAnchors; + } + + mAnchors.clear(); + + QTextDocument doc; + doc.setHtml(q->text()); + + // taken from QWidgetTextControl::setFocusToNextOrPreviousAnchor and QWidgetTextControl::findNextPrevAnchor + for (QTextBlock block = doc.begin(); block.isValid(); block = block.next()) { + QTextBlock::Iterator it = block.begin(); + + while (!it.atEnd()) { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) { + const int anchorStart = fragment.position(); + const QString anchorHref = fmt.anchorHref(); + int anchorEnd = -1; + + // find next non-anchor fragment + for (; !it.atEnd(); ++it) { + const QTextFragment fragment = it.fragment(); + const QTextCharFormat fmt = fragment.charFormat(); + + if (!fmt.isAnchor() || fmt.anchorHref() != anchorHref) { + anchorEnd = fragment.position(); + break; + } + } + + if (anchorEnd == -1) { + anchorEnd = block.position() + block.length() - 1; + } + + QTextCursor cursor{&doc}; + cursor.setPosition(anchorStart); + cursor.setPosition(anchorEnd, QTextCursor::KeepAnchor); + QString anchorText = cursor.selectedText(); + mAnchors.push_back({anchorStart, anchorEnd, anchorText, anchorHref}); + } else { + ++it; + } + } + } + + mAnchorsValid = true; + return mAnchors; +} + +int HtmlLabel::Private::anchorIndex(int start) +{ + anchors(); // ensure that the anchor cache is valid + auto it = std::find_if(std::cbegin(mAnchors), std::cend(mAnchors), [start](const auto &anchor) { + return anchor.start == start; + }); + if (it != std::cend(mAnchors)) { + return std::distance(std::cbegin(mAnchors), it); + } + return -1; +} + +void HtmlLabel::Private::invalidateAnchorCache() +{ + mAnchorsValid = false; } HtmlLabel::HtmlLabel(QWidget *parent) : HtmlLabel{{}, parent} { } HtmlLabel::HtmlLabel(const QString &html, QWidget *parent) : QLabel{parent} , d{new Private{this}} { setTextFormat(Qt::RichText); setTextInteractionFlags(Qt::TextBrowserInteraction); setHtml(html); } HtmlLabel::~HtmlLabel() = default; void HtmlLabel::setHtml(const QString &html) { if (html.isEmpty()) { clear(); + d->invalidateAnchorCache(); return; } d->updateText(html); } void HtmlLabel::setLinkColor(const QColor &color) { d->linkColor = color; d->updateText(); } +int HtmlLabel::numberOfAnchors() const +{ + return d->anchors().size(); +} + +QString HtmlLabel::anchorText(int index) const +{ + if (index >= 0 && index < numberOfAnchors()) { + return d->anchors()[index].text; + } + return {}; +} + +QString HtmlLabel::anchorHref(int index) const +{ + if (index >= 0 && index < numberOfAnchors()) { + return d->anchors()[index].href; + } + return {}; +} + +int HtmlLabel::selectedAnchor() const +{ + return d->anchorIndex(selectionStart()); +} + void HtmlLabel::focusInEvent(QFocusEvent *ev) { QLabel::focusInEvent(ev); // if the text label gets focus, then select its text; this is a workaround // for missing focus indicators for labels in many Qt styles const Qt::FocusReason reason = ev->reason(); const auto isKeyboardFocusEvent = reason == Qt::TabFocusReason || reason == Qt::BacktabFocusReason || reason == Qt::ShortcutFocusReason; if (!text().isEmpty() && isKeyboardFocusEvent) { Kleo::selectLabelText(this); } } bool HtmlLabel::focusNextPrevChild(bool next) { const bool result = QLabel::focusNextPrevChild(next); - if (hasFocus() && hasSelectedText()) { - QAccessibleTextSelectionEvent ev(this, selectionStart(), selectionStart() + selectedText().size()); - QAccessible::updateAccessibility(&ev); + if (hasFocus() && QAccessible::isActive()) { + const int anchorIndex = selectedAnchor(); + if (anchorIndex >= 0) { + QAccessibleEvent focusEvent(this, QAccessible::Focus); + focusEvent.setChild(anchorIndex); + QAccessible::updateAccessibility(&focusEvent); + } } return result; } diff --git a/src/view/htmllabel.h b/src/view/htmllabel.h index ab5818663..975a9daa6 100644 --- a/src/view/htmllabel.h +++ b/src/view/htmllabel.h @@ -1,44 +1,52 @@ /* view/htmllabel.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once +#include + #include #include namespace Kleo { -class HtmlLabel : public QLabel +class HtmlLabel : public QLabel, public AnchorProvider { Q_OBJECT public: explicit HtmlLabel(QWidget *parent = nullptr); explicit HtmlLabel(const QString &html, QWidget *parent = nullptr); ~HtmlLabel() override; void setHtml(const QString &html); void setLinkColor(const QColor &color); + // AnchorProvider + int numberOfAnchors() const override; + QString anchorText(int index) const override; + QString anchorHref(int index) const override; + int selectedAnchor() const override; + protected: void focusInEvent(QFocusEvent *ev) override; bool focusNextPrevChild(bool next) override; private: using QLabel::setText; private: class Private; std::unique_ptr d; }; }