File indexing completed on 2024-05-12 07:53:37
0001 /* 0002 SPDX-FileCopyrightText: 2010 Sebastian Sauer <mail@dipe.org> 0003 SPDX-FileCopyrightText: 2012 Frederik Gladhorn <gladhorn@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #ifndef _KATE_VIEW_ACCESSIBLE_ 0009 #define _KATE_VIEW_ACCESSIBLE_ 0010 0011 #ifndef QT_NO_ACCESSIBILITY 0012 0013 #include "katedocument.h" 0014 #include "kateview.h" 0015 #include "kateviewinternal.h" 0016 0017 #include <KLocalizedString> 0018 #include <QAccessible> 0019 #include <QAccessibleWidget> 0020 #include <QTextBoundaryFinder> 0021 0022 /** 0023 * This class implements a QAccessible-interface for a KateViewInternal. 0024 * 0025 * This is the root class for the kateview. The \a KateCursorAccessible class 0026 * represents the cursor in the kateview and is a child of this class. 0027 */ 0028 class KateViewAccessible : public QAccessibleWidget, public QAccessibleTextInterface, public QAccessibleEditableTextInterface 0029 { 0030 public: 0031 explicit KateViewAccessible(KateViewInternal *view) 0032 : QAccessibleWidget(view, QAccessible::EditableText) 0033 , m_lastPosition(-1) 0034 { 0035 // to invalidate positionFromCursor cache when the document is changed 0036 m_conn = QObject::connect(view->view()->document(), &KTextEditor::Document::textChanged, [this]() { 0037 m_lastPosition = -1; 0038 }); 0039 } 0040 0041 void *interface_cast(QAccessible::InterfaceType t) override 0042 { 0043 if (t == QAccessible::EditableTextInterface) 0044 return static_cast<QAccessibleEditableTextInterface *>(this); 0045 if (t == QAccessible::TextInterface) 0046 return static_cast<QAccessibleTextInterface *>(this); 0047 return nullptr; 0048 } 0049 0050 ~KateViewAccessible() override 0051 { 0052 QObject::disconnect(m_conn); 0053 } 0054 0055 QAccessibleInterface *childAt(int x, int y) const override 0056 { 0057 Q_UNUSED(x); 0058 Q_UNUSED(y); 0059 return nullptr; 0060 } 0061 0062 void setText(QAccessible::Text t, const QString &text) override 0063 { 0064 if (t == QAccessible::Value && view()->view()->document()) { 0065 view()->view()->document()->setText(text); 0066 m_lastPosition = -1; 0067 } 0068 } 0069 0070 QAccessible::State state() const override 0071 { 0072 QAccessible::State s = QAccessibleWidget::state(); 0073 s.focusable = view()->focusPolicy() != Qt::NoFocus; 0074 s.focused = view()->hasFocus(); 0075 s.editable = true; 0076 s.multiLine = true; 0077 s.selectableText = true; 0078 return s; 0079 } 0080 0081 QString text(QAccessible::Text t) const override 0082 { 0083 QString s; 0084 if (view()->view()->document()) { 0085 if (t == QAccessible::Name) { 0086 s = view()->view()->document()->documentName(); 0087 } 0088 if (t == QAccessible::Value) { 0089 s = view()->view()->document()->text(); 0090 } 0091 } 0092 return s; 0093 } 0094 0095 int characterCount() const override 0096 { 0097 return view()->view()->document()->text().size(); 0098 } 0099 0100 void addSelection(int startOffset, int endOffset) override 0101 { 0102 KTextEditor::Range range; 0103 range.setRange(cursorFromInt(startOffset), cursorFromInt(endOffset)); 0104 view()->view()->setSelection(range); 0105 view()->view()->setCursorPosition(cursorFromInt(endOffset)); 0106 } 0107 0108 QString attributes(int offset, int *startOffset, int *endOffset) const override 0109 { 0110 Q_UNUSED(offset); 0111 *startOffset = 0; 0112 *endOffset = characterCount(); 0113 return QString(); 0114 } 0115 0116 QRect characterRect(int offset) const override 0117 { 0118 KTextEditor::Cursor c = cursorFromInt(offset); 0119 if (!c.isValid()) { 0120 return QRect(); 0121 } 0122 QPoint p = view()->cursorToCoordinate(c); 0123 KTextEditor::Cursor endCursor = KTextEditor::Cursor(c.line(), c.column() + 1); 0124 QPoint size = view()->cursorToCoordinate(endCursor) - p; 0125 return QRect(view()->mapToGlobal(p), QSize(size.x(), size.y())); 0126 } 0127 0128 int cursorPosition() const override 0129 { 0130 KTextEditor::Cursor c = view()->cursorPosition(); 0131 return positionFromCursor(view(), c); 0132 } 0133 0134 int offsetAtPoint(const QPoint &point) const override 0135 { 0136 if (view()) { 0137 KTextEditor::Cursor c = view()->coordinatesToCursor(point); 0138 return positionFromCursor(view(), c); 0139 } 0140 return 0; 0141 } 0142 0143 void removeSelection(int selectionIndex) override 0144 { 0145 if (selectionIndex != 0) { 0146 return; 0147 } 0148 view()->view()->clearSelection(); 0149 } 0150 0151 void scrollToSubstring(int startIndex, int /*endIndex*/) override 0152 { 0153 auto c = cursorFromInt(startIndex); 0154 if (!c.isValid()) { 0155 return; 0156 } 0157 view()->view()->setScrollPosition(c); 0158 } 0159 0160 void selection(int selectionIndex, int *startOffset, int *endOffset) const override 0161 { 0162 if (selectionIndex != 0 || !view()->view()->selection()) { 0163 *startOffset = 0; 0164 *endOffset = 0; 0165 return; 0166 } 0167 KTextEditor::Range range = view()->view()->selectionRange(); 0168 *startOffset = positionFromCursor(view(), range.start()); 0169 *endOffset = positionFromCursor(view(), range.end()); 0170 } 0171 0172 int selectionCount() const override 0173 { 0174 return view()->view()->selection() ? 1 : 0; 0175 } 0176 0177 void setCursorPosition(int position) override 0178 { 0179 view()->view()->setCursorPosition(cursorFromInt(position)); 0180 } 0181 0182 void setSelection(int selectionIndex, int startOffset, int endOffset) override 0183 { 0184 if (selectionIndex != 0) { 0185 return; 0186 } 0187 KTextEditor::Range range = KTextEditor::Range(cursorFromInt(startOffset), cursorFromInt(endOffset)); 0188 view()->view()->setSelection(range); 0189 } 0190 0191 QString text(int startOffset, int endOffset) const override 0192 { 0193 if (startOffset > endOffset) { 0194 return QString(); 0195 } 0196 return view()->view()->document()->text().mid(startOffset, endOffset - startOffset); 0197 } 0198 0199 QString textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType, int *startOffset, int *endOffset) const override 0200 { 0201 *startOffset = -1; 0202 *endOffset = -1; 0203 if (!view() || !startOffset || !endOffset) { 0204 return {}; 0205 } 0206 if (offset == -1) { 0207 offset = positionFromCursor(view(), view()->view()->doc()->documentEnd()); 0208 } 0209 KTextEditor::Cursor c = cursorFromInt(offset); 0210 if (!c.isValid()) { 0211 return {}; 0212 } 0213 auto doc = view()->view()->doc(); 0214 0215 switch (boundaryType) { 0216 case QAccessible::TextBoundaryType::CharBoundary: { 0217 QString t = doc->characterAt(c); 0218 *startOffset = offset; 0219 *endOffset = *startOffset + 1; 0220 return t; 0221 } break; 0222 case QAccessible::TextBoundaryType::WordBoundary: { 0223 QString t = doc->wordAt(c); 0224 *startOffset = offset; 0225 *endOffset = offset + t.size(); 0226 return t; 0227 } break; 0228 case QAccessible::TextBoundaryType::LineBoundary: 0229 case QAccessible::TextBoundaryType::ParagraphBoundary: { 0230 const QString line = doc->line(c.line()); 0231 if (line.isEmpty()) { 0232 *startOffset = offset; 0233 *endOffset = offset; 0234 return {}; 0235 } 0236 const int start = positionFromCursor(view(), KTextEditor::Cursor(c.line(), 0)); 0237 *startOffset = start; 0238 *endOffset = offset + line.size(); 0239 return line; 0240 } break; 0241 case QAccessible::TextBoundaryType::SentenceBoundary: { 0242 const QString line = doc->line(c.line()); 0243 if (line.isEmpty()) { 0244 *startOffset = offset; 0245 *endOffset = offset; 0246 return {}; 0247 } 0248 QTextBoundaryFinder bf(QTextBoundaryFinder::BoundaryType::Sentence, line); 0249 int e = bf.toNextBoundary(); 0250 if (e != -1) { 0251 int start = positionFromCursor(view(), KTextEditor::Cursor(c.line(), 0)); 0252 *startOffset = start; 0253 *endOffset = start + bf.position(); 0254 return line.mid(0, e); 0255 } 0256 } break; 0257 case QAccessible::TextBoundaryType::NoBoundary: { 0258 const QString text = doc->text(); 0259 *startOffset = 0; 0260 *endOffset = text.size(); 0261 return text; 0262 } break; 0263 } 0264 0265 return {}; 0266 } 0267 0268 QString textBeforeOffset(int /*offset*/, QAccessible::TextBoundaryType /*boundaryType*/, int *startOffset, int *endOffset) const override 0269 { 0270 // FIXME 0271 *startOffset = -1; 0272 *endOffset = -1; 0273 return {}; 0274 } 0275 QString textAfterOffset(int /*offset*/, QAccessible::TextBoundaryType /*boundaryType*/, int *startOffset, int *endOffset) const override 0276 { 0277 // FIXME 0278 *startOffset = -1; 0279 *endOffset = -1; 0280 return {}; 0281 } 0282 0283 void deleteText(int startOffset, int endOffset) override 0284 { 0285 KTextEditor::Document *document = view()->view()->document(); 0286 KTextEditor::Range range(document->offsetToCursor(startOffset), document->offsetToCursor(endOffset)); 0287 document->removeText(range); 0288 } 0289 0290 void insertText(int offset, const QString &text) override 0291 { 0292 KTextEditor::Document *document = view()->view()->document(); 0293 KTextEditor::Cursor cursor = document->offsetToCursor(offset); 0294 document->insertText(cursor, text); 0295 } 0296 0297 void replaceText(int startOffset, int endOffset, const QString &text) override 0298 { 0299 KTextEditor::Document *document = view()->view()->document(); 0300 KTextEditor::Range range(document->offsetToCursor(startOffset), document->offsetToCursor(endOffset)); 0301 document->replaceText(range, text); 0302 } 0303 0304 /** 0305 * When possible, using the last returned value m_lastPosition do the count 0306 * from the last cursor position m_lastCursor. 0307 * @return the number of chars (including one character for new lines) 0308 * from the beginning of the file. 0309 */ 0310 int positionFromCursor(KateViewInternal *view, KTextEditor::Cursor cursor) const 0311 { 0312 int pos = m_lastPosition; 0313 const KTextEditor::DocumentPrivate *doc = view->view()->doc(); 0314 0315 // m_lastPosition < 0 is invalid, calculate from the beginning of the document 0316 if (m_lastPosition < 0 || view != m_lastView) { 0317 pos = doc->cursorToOffset(cursor) - cursor.column(); 0318 } else { 0319 // if the lines are the same, just add the cursor.column(), otherwise 0320 if (cursor.line() != m_lastCursor.line()) { 0321 // If the cursor is after the previous cursor 0322 if (m_lastCursor.line() < cursor.line()) { 0323 for (int line = m_lastCursor.line(); line < cursor.line(); ++line) { 0324 pos += doc->lineLength(line); 0325 } 0326 // add new line character for each line 0327 pos += cursor.line() - m_lastCursor.line(); 0328 } else { 0329 for (int line = cursor.line(); line < m_lastCursor.line(); ++line) { 0330 pos -= doc->lineLength(line); 0331 } 0332 // remove new line character for each line 0333 pos -= m_lastCursor.line() - cursor.line(); 0334 } 0335 } 0336 } 0337 m_lastCursor = cursor; 0338 m_lastPosition = pos; 0339 0340 return pos + cursor.column(); 0341 } 0342 0343 private: 0344 inline KateViewInternal *view() const 0345 { 0346 return static_cast<KateViewInternal *>(object()); 0347 } 0348 0349 KTextEditor::Cursor cursorFromInt(int position) const 0350 { 0351 return view()->view()->doc()->offsetToCursor(position); 0352 } 0353 0354 QString textLine(int shiftLines, int offset, int *startOffset, int *endOffset) const 0355 { 0356 KTextEditor::Cursor pos = cursorFromInt(offset); 0357 pos.setColumn(0); 0358 if (shiftLines) { 0359 pos.setLine(pos.line() + shiftLines); 0360 } 0361 *startOffset = positionFromCursor(view(), pos); 0362 QString line = view()->view()->document()->line(pos.line()) + QLatin1Char('\n'); 0363 *endOffset = *startOffset + line.length(); 0364 return line; 0365 } 0366 0367 private: 0368 // Cache data for positionFromCursor 0369 mutable KateViewInternal *m_lastView; 0370 mutable KTextEditor::Cursor m_lastCursor; 0371 // m_lastPosition stores the positionFromCursor, with the cursor always in column 0 0372 mutable int m_lastPosition; 0373 // to disconnect the signal 0374 QMetaObject::Connection m_conn; 0375 }; 0376 0377 /** 0378 * Factory-function used to create \a KateViewAccessible instances for KateViewInternal 0379 * to make the KateViewInternal accessible. 0380 */ 0381 QAccessibleInterface *accessibleInterfaceFactory(const QString &key, QObject *object) 0382 { 0383 Q_UNUSED(key) 0384 // if (key == QLatin1String("KateViewInternal")) 0385 if (KateViewInternal *view = qobject_cast<KateViewInternal *>(object)) { 0386 return new KateViewAccessible(view); 0387 } 0388 return nullptr; 0389 } 0390 0391 #endif 0392 #endif