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 }