File indexing completed on 2024-06-23 05:13:36

0001 /*
0002     accessibility/accessiblerichtextlabel.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2021 g10 Code GmbH
0006     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include <config-kleopatra.h>
0012 
0013 #include "accessiblerichtextlabel_p.h"
0014 
0015 #include "accessiblelink_p.h"
0016 
0017 #include <interfaces/anchorprovider.h>
0018 
0019 #include <QLabel>
0020 #include <QTextDocument>
0021 
0022 using namespace Kleo;
0023 
0024 struct AccessibleRichTextLabel::ChildData {
0025     QAccessible::Id id = 0;
0026 };
0027 
0028 AccessibleRichTextLabel::AccessibleRichTextLabel(QWidget *w)
0029     : QAccessibleWidget{w, QAccessible::StaticText}
0030 {
0031     Q_ASSERT(qobject_cast<QLabel *>(w));
0032 }
0033 
0034 AccessibleRichTextLabel::~AccessibleRichTextLabel()
0035 {
0036     clearChildCache();
0037 }
0038 
0039 void *AccessibleRichTextLabel::interface_cast(QAccessible::InterfaceType t)
0040 {
0041     if (t == QAccessible::TextInterface) {
0042         return static_cast<QAccessibleTextInterface *>(this);
0043     }
0044     return QAccessibleWidget::interface_cast(t);
0045 }
0046 
0047 QAccessible::State AccessibleRichTextLabel::state() const
0048 {
0049     QAccessible::State state = QAccessibleWidget::state();
0050     state.readOnly = true;
0051     state.selectableText = true;
0052     return state;
0053 }
0054 
0055 QString AccessibleRichTextLabel::text(QAccessible::Text t) const
0056 {
0057     QString str;
0058     switch (t) {
0059     case QAccessible::Name:
0060         str = widget()->accessibleName();
0061         if (str.isEmpty()) {
0062             str = displayText();
0063         }
0064         break;
0065     default:
0066         break;
0067     }
0068     if (str.isEmpty()) {
0069         str = QAccessibleWidget::text(t);
0070     }
0071     return str;
0072 }
0073 
0074 QAccessibleInterface *AccessibleRichTextLabel::focusChild() const
0075 {
0076     if (const auto *const ap = anchorProvider()) {
0077         const int childIndex = ap->selectedAnchor();
0078         if (childIndex >= 0) {
0079             return child(childIndex);
0080         }
0081     }
0082     return QAccessibleWidget::focusChild();
0083 }
0084 
0085 QAccessibleInterface *AccessibleRichTextLabel::child(int index) const
0086 {
0087     const auto *const ap = anchorProvider();
0088     if (ap && index >= 0 && index < ap->numberOfAnchors()) {
0089         auto &childData = childCache()[index];
0090         if (childData.id != 0) {
0091             return QAccessible::accessibleInterface(childData.id);
0092         }
0093 
0094         QAccessibleInterface *iface = new AccessibleLink{widget(), index};
0095         childData.id = QAccessible::registerAccessibleInterface(iface);
0096         return iface;
0097     }
0098     return nullptr;
0099 }
0100 
0101 int AccessibleRichTextLabel::childCount() const
0102 {
0103     if (const auto *const ap = anchorProvider()) {
0104         return ap->numberOfAnchors();
0105     }
0106     return 0;
0107 }
0108 
0109 int AccessibleRichTextLabel::indexOfChild(const QAccessibleInterface *child) const
0110 {
0111     if ((child->role() == QAccessible::Link) && (child->parent() == this)) {
0112         return static_cast<const AccessibleLink *>(child)->index();
0113     }
0114     return -1;
0115 }
0116 
0117 void AccessibleRichTextLabel::selection(int selectionIndex, int *startOffset, int *endOffset) const
0118 {
0119     *startOffset = *endOffset = 0;
0120     if (selectionIndex != 0)
0121         return;
0122 
0123     *startOffset = label()->selectionStart();
0124     *endOffset = *startOffset + label()->selectedText().size();
0125 }
0126 
0127 int AccessibleRichTextLabel::selectionCount() const
0128 {
0129     return label()->hasSelectedText() ? 1 : 0;
0130 }
0131 
0132 void AccessibleRichTextLabel::addSelection(int startOffset, int endOffset)
0133 {
0134     setSelection(0, startOffset, endOffset);
0135 }
0136 
0137 void AccessibleRichTextLabel::removeSelection(int selectionIndex)
0138 {
0139     if (selectionIndex != 0)
0140         return;
0141 
0142     label()->setSelection(-1, -1);
0143 }
0144 
0145 void AccessibleRichTextLabel::setSelection(int selectionIndex, int startOffset, int endOffset)
0146 {
0147     if (selectionIndex != 0)
0148         return;
0149 
0150     label()->setSelection(startOffset, endOffset - startOffset);
0151 }
0152 
0153 int AccessibleRichTextLabel::cursorPosition() const
0154 {
0155     return label()->hasSelectedText() ? label()->selectionStart() + label()->selectedText().size() : 0;
0156 }
0157 
0158 void AccessibleRichTextLabel::setCursorPosition(int position)
0159 {
0160     Q_UNUSED(position)
0161 }
0162 
0163 QString AccessibleRichTextLabel::text(int startOffset, int endOffset) const
0164 {
0165     if (startOffset > endOffset)
0166         return {};
0167 
0168     // most likely the client is asking for the selected text, so return it
0169     // instead of a slice of displayText() if the offsets match the selection
0170     if (startOffset == label()->selectionStart() //
0171         && endOffset == startOffset + label()->selectedText().size()) {
0172         return label()->selectedText();
0173     }
0174     return displayText().mid(startOffset, endOffset - startOffset);
0175 }
0176 
0177 int AccessibleRichTextLabel::characterCount() const
0178 {
0179     return displayText().size();
0180 }
0181 
0182 QRect AccessibleRichTextLabel::characterRect(int offset) const
0183 {
0184     Q_UNUSED(offset)
0185     return {};
0186 }
0187 
0188 int AccessibleRichTextLabel::offsetAtPoint(const QPoint &point) const
0189 {
0190     Q_UNUSED(point)
0191     return -1;
0192 }
0193 
0194 QString AccessibleRichTextLabel::attributes(int offset, int *startOffset, int *endOffset) const
0195 {
0196     *startOffset = *endOffset = offset;
0197     return {};
0198 }
0199 
0200 void AccessibleRichTextLabel::scrollToSubstring(int startIndex, int endIndex)
0201 {
0202     Q_UNUSED(startIndex)
0203     Q_UNUSED(endIndex)
0204 }
0205 
0206 QLabel *AccessibleRichTextLabel::label() const
0207 {
0208     return qobject_cast<QLabel *>(object());
0209 }
0210 
0211 AnchorProvider *AccessibleRichTextLabel::anchorProvider() const
0212 {
0213     return dynamic_cast<AnchorProvider *>(object());
0214 }
0215 
0216 QString AccessibleRichTextLabel::displayText() const
0217 {
0218     // calculate an approximation of the displayed text without using private
0219     // information of QLabel
0220     QString str = label()->text();
0221     if (label()->textFormat() == Qt::RichText //
0222         || (label()->textFormat() == Qt::AutoText && Qt::mightBeRichText(str))) {
0223         QTextDocument doc;
0224         doc.setHtml(str);
0225         str = doc.toPlainText();
0226     }
0227     return str;
0228 }
0229 
0230 std::vector<AccessibleRichTextLabel::ChildData> &AccessibleRichTextLabel::childCache() const
0231 {
0232     const auto *const ap = anchorProvider();
0233     if (!ap || static_cast<int>(mChildCache.size()) == ap->numberOfAnchors()) {
0234         return mChildCache;
0235     }
0236 
0237     clearChildCache();
0238     // fill the cache with default-initialized child data
0239     mChildCache.resize(ap->numberOfAnchors());
0240 
0241     return mChildCache;
0242 }
0243 
0244 void AccessibleRichTextLabel::clearChildCache() const
0245 {
0246     std::for_each(std::cbegin(mChildCache), std::cend(mChildCache), [](const auto &child) {
0247         if (child.id != 0) {
0248             QAccessible::deleteAccessibleInterface(child.id);
0249         }
0250     });
0251     mChildCache.clear();
0252 }