Commit eaae2c47 authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Improve accessibility of Diagnostics/Show Audit Log label

* Add UrlLabel, a label for displaying a single link, which
  * automatically gives focus to/selects the displayed link when the
    label gets focus,
  * sends a text selection event to the accessibility clients when
    a link is selected,
  * works around a bug in QLabel::focusNextPrevChild(false)
* Add AccessibleRichTextLabel which allows accessibility clients to
  retrieve the selected text of a UrlLabel
* Use UrlLabel for the Diagnostics/Show Audit Log label

GnuPG-bug-id: 5535
parent 04170a83
Pipeline #77785 passed with stage
in 15 minutes and 26 seconds
......@@ -110,7 +110,8 @@ endif()
find_package(KF5Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED)
find_package(KF5Mime ${KMIME_WANT_VERSION} CONFIG REQUIRED)
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test Network PrintSupport)
find_package(Qt5Widgets ${QT_REQUIRED_VERSION} CONFIG REQUIRED Private)
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Test Network PrintSupport)
find_package(Assuan2 REQUIRED)
......
......@@ -2,6 +2,7 @@ add_subdirectory(icons)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(SYSTEM ${Qt5Widgets_PRIVATE_INCLUDE_DIRS})
if (NOT DISABLE_KWATCHGNUPG)
add_subdirectory(kwatchgnupg)
......@@ -124,6 +125,7 @@ set(_kleopatra_SRCS
view/nullpinwidget.cpp
view/tabwidget.cpp
view/keycacheoverlay.cpp
view/urllabel.cpp
view/waitwidget.cpp
view/welcomewidget.cpp
......@@ -280,6 +282,9 @@ set(_kleopatra_SRCS
${_kleopatra_deviceinfowatcher_files}
accessibility/accessiblerichtextlabel.cpp
accessibility/accessiblewidgetfactory.cpp
aboutdata.cpp
systrayicon.cpp
kleopatraapplication.cpp
......
/*
accessibility/accessiblerichtextlabel.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "accessiblerichtextlabel_p.h"
#include <QLabel>
#include <QTextDocument>
using namespace Kleo;
AccessibleRichTextLabel::AccessibleRichTextLabel(QWidget *w)
: QAccessibleWidget{w, QAccessible::StaticText}
{
Q_ASSERT(qobject_cast<QLabel *>(w));
}
void *AccessibleRichTextLabel::interface_cast(QAccessible::InterfaceType t)
{
if (t == QAccessible::TextInterface)
return static_cast<QAccessibleTextInterface *>(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())
str = QAccessibleWidget::text(t);
return str;
}
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<QLabel*>(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;
}
/*
accessibility/accessiblerichtextlabel_p.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QAccessibleWidget>
class QLabel;
namespace Kleo
{
class AccessibleRichTextLabel : public QAccessibleWidget, public QAccessibleTextInterface
{
public:
explicit AccessibleRichTextLabel(QWidget *o);
void *interface_cast(QAccessible::InterfaceType t) override;
QAccessible::State state() const override;
QString text(QAccessible::Text t) 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:
QLabel *label() const;
QString displayText() const;
};
}
/*
accessibility/accessiblewidgetfactory.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "accessiblewidgetfactory.h"
#include "accessiblerichtextlabel_p.h"
#include "view/urllabel.h"
#include "private/qwidget_p.h"
QAccessibleInterface *Kleo::accessibleWidgetFactory(const QString &classname, QObject *object)
{
QAccessibleInterface *iface = nullptr;
if (!object || !object->isWidgetType())
return iface;
QWidget *widget = static_cast<QWidget*>(object);
// QWidget emits destroyed() from its destructor instead of letting the QObject
// destructor do it, which means the QWidget is unregistered from the accessibility
// cache. But QWidget destruction also emits enter and leave events, which may end
// up here, so we have to ensure that we don't fill the cache with an entry of
// a widget that is going away.
if (QWidgetPrivate::get(widget)->data.in_destructor)
return iface;
if (classname == QString::fromLatin1(Kleo::UrlLabel::staticMetaObject.className())) {
iface = new AccessibleRichTextLabel{widget};
}
return iface;
}
/*
accessibility/accessiblewidgetfactory.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QAccessibleInterface>
namespace Kleo
{
QAccessibleInterface *accessibleWidgetFactory(const QString &classname, QObject *object);
}
......@@ -19,6 +19,7 @@
#include "commands/importcertificatefromfilecommand.h"
#include "commands/lookupcertificatescommand.h"
#include "crypto/decryptverifytask.h"
#include "view/urllabel.h"
#include <Libkleo/MessageBox>
#include <Libkleo/Classify>
......@@ -95,7 +96,7 @@ public:
const std::shared_ptr<const Task::Result> m_result;
QLabel *m_detailsLabel = nullptr;
QLabel *m_actionsLabel = nullptr;
UrlLabel *m_auditLogLabel = nullptr;
QPushButton *m_closeButton = nullptr;
bool m_importCanceled = false;
};
......@@ -222,18 +223,13 @@ static QUrl auditlog_url_template()
void ResultItemWidget::Private::updateShowDetailsLabel()
{
if (!m_actionsLabel || !m_detailsLabel) {
return;
}
const auto parentTask = m_result->parentTask();
QString auditLogLink;
if (m_result->hasError()) {
auditLogLink = m_result->auditLog().formatLink(auditlog_url_template(), i18n("Diagnostics"));
} else {
auditLogLink = m_result->auditLog().formatLink(auditlog_url_template());
}
m_actionsLabel->setText(auditLogLink);
const auto auditLogUrl = m_result->auditLog().asUrl(auditlog_url_template());
const auto auditLogLinkText =
m_result->hasError() ? i18n("Diagnostics")
: i18nc("The Audit Log is a detailed error log from the gnupg backend",
"Show Audit Log");
m_auditLogLabel->setUrl(auditLogUrl, auditLogLinkText);
m_auditLogLabel->setVisible(!auditLogUrl.isEmpty());
}
ResultItemWidget::ResultItemWidget(const std::shared_ptr<const Task::Result> &result, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), d(new Private(result, this))
......@@ -272,12 +268,11 @@ ResultItemWidget::ResultItemWidget(const std::shared_ptr<const Task::Result> &re
d->addIgnoreMDCButton(actionLayout);
d->m_actionsLabel = new QLabel;
connect(d->m_actionsLabel, SIGNAL(linkActivated(QString)), this, SLOT(slotLinkActivated(QString)));
actionLayout->addWidget(d->m_actionsLabel);
d->m_actionsLabel->setFocusPolicy(Qt::StrongFocus);
d->m_actionsLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
d->m_actionsLabel->setStyleSheet(styleSheet);
d->m_auditLogLabel = new UrlLabel;
connect(d->m_auditLogLabel, &UrlLabel::linkActivated,
this, [this](const auto &link) { d->slotLinkActivated(link); });
actionLayout->addWidget(d->m_auditLogLabel);
d->m_auditLogLabel->setStyleSheet(styleSheet);
d->m_detailsLabel = new QLabel;
d->m_detailsLabel->setWordWrap(true);
......
......@@ -16,6 +16,8 @@
#include "kleopatraapplication.h"
#include "mainwindow.h"
#include "accessibility/accessiblewidgetfactory.h"
#include <kcoreaddons_version.h>
#if KCOREADDONS_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <Kdelibs4ConfigMigrator>
......@@ -56,6 +58,7 @@
#include <KMessageBox>
#include <KCrash>
#include <QAccessible>
#include <QTextDocument> // for Qt::escape
#include <QMessageBox>
#include <QTimer>
......@@ -100,7 +103,7 @@ int main(int argc, char **argv)
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
KleopatraApplication app(argc, argv);
KCrash::initialize();
QAccessible::installFactory(Kleo::accessibleWidgetFactory);
QElapsedTimer timer;
timer.start();
......
......@@ -29,7 +29,7 @@ AuditLog AuditLog::fromJob(const QGpgME::Job *job)
}
}
QString AuditLog::formatLink(const QUrl &urlTemplate, const QString &caption) const
QUrl AuditLog::asUrl(const QUrl &urlTemplate) const
{
// more or less the same as
// kmail/objecttreeparser.cpp:makeShowAuditLogLink(), so any bug
......@@ -42,19 +42,16 @@ QString AuditLog::formatLink(const QUrl &urlTemplate, const QString &caption) co
} else {
qCDebug(KLEOPATRA_LOG) << "Error Retrieving Audit Log:" << QString::fromLocal8Bit(m_error.asString());
}
return QString();
return {};
}
if (!m_text.isEmpty()) {
QUrl url = urlTemplate;
QUrlQuery urlQuery(url);
urlQuery.addQueryItem(QStringLiteral("log"), m_text);
url.setQuery(urlQuery);
return QLatin1String("<a href=\"") + url.url() + QLatin1String("\">") +
(caption.isNull() ? i18nc("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log") : caption) +
QLatin1String("</a>");
if (m_text.isEmpty()) {
return {};
}
return QString();
QUrl url = urlTemplate;
QUrlQuery urlQuery{url};
urlQuery.addQueryItem(QStringLiteral("log"), m_text);
url.setQuery(urlQuery);
return url;
}
......@@ -43,7 +43,7 @@ public:
return m_text;
}
QString formatLink(const QUrl &urlTemplate, const QString &caption = QString()) const;
QUrl asUrl(const QUrl &urlTemplate) const;
private:
QString m_text;
......
/*
view/urllabel.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "urllabel.h"
#include <QAccessible>
using namespace Kleo;
UrlLabel::UrlLabel(QWidget *parent)
: QLabel{parent}
{
setTextFormat(Qt::RichText);
setTextInteractionFlags(Qt::TextBrowserInteraction);
}
void UrlLabel::setUrl(const QUrl &url, const QString &text)
{
// we prepend a zero-width-space character to work around a bug in QLabel::focusNextPrevChild(false)
// which makes it impossible to leave the label with Shift+Tab if the text starts with a link
static const QString templateString{QLatin1String{"&#8203;<a href=\"%1\">%2</a>"}};
if (url.isEmpty()) {
clear();
return;
}
setText(templateString.arg(url.url(QUrl::FullyEncoded), text.isEmpty() ? url.toDisplayString().toHtmlEscaped() : text.toHtmlEscaped()));
}
void UrlLabel::focusInEvent(QFocusEvent *event)
{
// immediately focus the URL when the label get focus
QLabel::focusInEvent(event);
if (!hasSelectedText()) {
focusNextPrevChild(true);
}
}
bool UrlLabel::focusNextPrevChild(bool next)
{
const bool result = QLabel::focusNextPrevChild(next);
if (hasFocus() && hasSelectedText()) {
QAccessibleTextSelectionEvent ev(this, selectionStart(), selectionStart() + selectedText().size());
QAccessible::updateAccessibility(&ev);
}
return result;
}
/*
view/urllabel.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QLabel>
namespace Kleo
{
class UrlLabel : public QLabel
{
Q_OBJECT
public:
explicit UrlLabel(QWidget *parent = nullptr);
void setUrl(const QUrl &url, const QString &text = {});
protected:
void focusInEvent(QFocusEvent *event) override;
bool focusNextPrevChild(bool next) override;
private:
using QLabel::setText;
};
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment