File indexing completed on 2024-04-14 03:55: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