Warning, file /pim/kleopatra/src/utils/accessibility.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*  utils/accessibility.cpp
0002 
0003     This file is part of Kleopatra, the KDE keymanager
0004     SPDX-FileCopyrightText: 2022 g10 Code GmbH
0005     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "accessibility.h"
0013 
0014 #include <KLocalizedString>
0015 
0016 #include <QAction>
0017 #ifdef Q_OS_WIN
0018 #include <QApplication>
0019 #include <QDesktopWidget>
0020 #endif
0021 #include <QLabel>
0022 #include <QTextDocument>
0023 #include <QToolTip>
0024 
0025 #include <algorithm>
0026 #include <chrono>
0027 
0028 #include "kleopatra_debug.h"
0029 
0030 using namespace Kleo;
0031 
0032 static const char *accessibleNameProperty = "_kleo_accessibleName";
0033 static const char *accessibleValueProperty = "_kleo_accessibleValue";
0034 
0035 static const char *useAccessibleValueLabelProperty = "_kleo_useAccessibleValueLabel";
0036 
0037 namespace
0038 {
0039 QString getAccessibleText(QWidget *widget, QAccessible::Text t)
0040 {
0041     QString name;
0042     if (const auto *const iface = QAccessible::queryAccessibleInterface(widget)) {
0043         name = iface->text(t);
0044     }
0045     return name;
0046 }
0047 }
0048 
0049 QString Kleo::getAccessibleName(QWidget *widget)
0050 {
0051     return getAccessibleText(widget, QAccessible::Name);
0052 }
0053 
0054 QString Kleo::getAccessibleDescription(QWidget *widget)
0055 {
0056     return getAccessibleText(widget, QAccessible::Description);
0057 }
0058 
0059 void Kleo::setAccessibleName(QAction *action, const QString &name)
0060 {
0061     action->setProperty(accessibleNameProperty, name);
0062 }
0063 
0064 QString Kleo::getAccessibleName(const QAction *action)
0065 {
0066     return action->property(accessibleNameProperty).toString();
0067 }
0068 
0069 void Kleo::setAccessibleValue(QWidget *widget, const QString &value)
0070 {
0071     widget->setProperty(accessibleValueProperty, value);
0072 }
0073 
0074 QString Kleo::getAccessibleValue(const QWidget *widget)
0075 {
0076     return widget->property(accessibleValueProperty).toString();
0077 }
0078 
0079 void Kleo::setRepresentAsAccessibleValueWidget(QWidget *widget, bool flag)
0080 {
0081     widget->setProperty(useAccessibleValueLabelProperty, flag ? flag : QVariant{});
0082 }
0083 
0084 bool Kleo::representAsAccessibleValueWidget(const QWidget *widget)
0085 {
0086     return widget->property(useAccessibleValueLabelProperty).toBool();
0087 }
0088 
0089 QString Kleo::invalidEntryText()
0090 {
0091     return i18nc(
0092         "text for screen readers to indicate that the associated object, "
0093         "such as a form field, has an error",
0094         "invalid entry");
0095 }
0096 
0097 QString Kleo::requiredText()
0098 {
0099     return i18nc(
0100         "text for screen readers to indicate that the associated object, "
0101         "such as a form field must be filled out",
0102         "required");
0103 }
0104 
0105 void Kleo::selectLabelText(QLabel *label)
0106 {
0107     if (!label || label->text().isEmpty()) {
0108         return;
0109     }
0110     if (label->textFormat() == Qt::PlainText) {
0111         label->setSelection(0, label->text().size());
0112     } else if (label->textFormat() == Qt::RichText) {
0113         // unfortunately, there is no selectAll(); therefore, we need
0114         // to determine the "visual" length of the text by stripping
0115         // the label's text of all formatting information
0116         QTextDocument temp;
0117         temp.setHtml(label->text());
0118         label->setSelection(0, temp.toRawText().size());
0119     } else {
0120         qCDebug(KLEOPATRA_LOG) << "Label with unsupported text format" << label->textFormat() << "got focus";
0121     }
0122 }
0123 
0124 namespace
0125 {
0126 static void notifyAccessibilityClientsAboutToolTip(const QPoint &pos, QWidget *parent)
0127 {
0128 #ifdef Q_OS_WIN
0129     // On Windows, the tool tip's parent widget is a desktop screen widget (see implementation of QToolTip::showText)
0130     QT_WARNING_PUSH
0131     QT_WARNING_DISABLE_DEPRECATED
0132     const auto desktop = QApplication::desktop();
0133     const int screenNumber = desktop->isVirtualDesktop() ? desktop->screenNumber(pos) : desktop->screenNumber(parent);
0134     parent = desktop->screen(screenNumber);
0135     QT_WARNING_POP
0136 #else
0137     Q_UNUSED(pos);
0138 #endif
0139     if (!parent) {
0140         return;
0141     }
0142     if (auto toolTipLabel = parent->findChild<QLabel *>(QStringLiteral("qtooltip_label"))) {
0143         // Qt explicitly does not notify accessibility clients about the tool tip being shown because
0144         // "Tooltips are read aloud twice in MS narrator."
0145         // The problem is that they are not read out by orca (on Linux) if the notification is omitted.
0146         // Therefore, we take care of notifying the accessibility clients.
0147 #ifndef QT_NO_ACCESSIBILITY
0148         QAccessibleEvent event(toolTipLabel, QAccessible::ObjectShow);
0149         QAccessible::updateAccessibility(&event);
0150 #endif
0151     }
0152 }
0153 }
0154 
0155 void Kleo::showToolTip(const QPoint &pos, const QString &text, QWidget *w)
0156 {
0157     using namespace std::chrono_literals;
0158     static const std::chrono::milliseconds timeout = 24h;
0159     QToolTip::showText(pos, text, w, {}, timeout.count());
0160     notifyAccessibilityClientsAboutToolTip(pos, w);
0161 }
0162 
0163 LabelHelper::LabelHelper()
0164 {
0165     QAccessible::installActivationObserver(this);
0166 }
0167 
0168 LabelHelper::~LabelHelper()
0169 {
0170     QAccessible::removeActivationObserver(this);
0171 }
0172 
0173 void LabelHelper::addLabel(QLabel *label)
0174 {
0175     mLabels.push_back(label);
0176     accessibilityActiveChanged(QAccessible::isActive());
0177 }
0178 
0179 void LabelHelper::accessibilityActiveChanged(bool active)
0180 {
0181     // Allow text labels to get focus if accessibility is active
0182     const auto focusPolicy = active ? Qt::StrongFocus : Qt::ClickFocus;
0183     std::for_each(std::cbegin(mLabels), std::cend(mLabels), [focusPolicy](const auto &label) {
0184         if (label) {
0185             label->setFocusPolicy(focusPolicy);
0186         }
0187     });
0188 }