File indexing completed on 2025-02-23 04:35:30

0001 /*
0002     SPDX-FileCopyrightText: 2020 The Qt Company Ltd.
0003     SPDX-FileCopyrightText: 2020-2022 Mladen Milinkovic <max@smoothware.net>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #ifndef RICHDOCUMENTEDITOR_H
0009 #define RICHDOCUMENTEDITOR_H
0010 
0011 // based on Qt 5.15.2 private QWidgetLineControl and QLineEdit
0012 
0013 #include <QtGlobal>
0014 
0015 #include <QClipboard>
0016 #include <QCompleter>
0017 #include <QInputMethod>
0018 #include <QPoint>
0019 #include <QPointer>
0020 #include <QTextLayout>
0021 #include <QTextDocumentFragment>
0022 
0023 #include <vector>
0024 
0025 #include "core/richtext/richdocument.h"
0026 
0027 #ifdef DrawText
0028 #  undef DrawText
0029 #endif
0030 
0031 namespace SubtitleComposer {
0032 
0033 class RichDocumentEditor : public QObject
0034 {
0035     Q_OBJECT
0036 
0037 public:
0038     RichDocumentEditor();
0039 
0040     bool isAcceptableInput(const QKeyEvent *event) const;
0041     static bool isCommonTextEditShortcut(const QKeyEvent *ke);
0042 
0043     void setAccessibleObject(QObject *object)
0044     {
0045         Q_ASSERT(object);
0046         m_accessibleObject = object;
0047     }
0048 
0049     QObject *accessibleObject()
0050     {
0051         if(m_accessibleObject)
0052             return m_accessibleObject;
0053         return parent();
0054     }
0055 
0056     bool hasSelection() const { return m_textCursor && m_textCursor->hasSelection(); }
0057 
0058     inline int width() const { return qRound(m_layoutWidth); }
0059     inline int height() const { return qRound(m_layoutHeight); }
0060     inline int ascent() const { return m_ascent; }
0061     inline qreal naturalTextWidth() const { return m_layoutWidth; }
0062 
0063     void setSelection(int start, int length);
0064 
0065     inline QString selectedText() const { return m_textCursor ? m_textCursor->selectedText() : QString(); }
0066     inline QTextDocumentFragment selection() const { return m_textCursor ? m_textCursor->selection() : QTextDocumentFragment(); }
0067 
0068     int selectionStart() const { return m_textCursor ? m_textCursor->selectionStart() : -1; }
0069     int selectionEnd() const { return m_textCursor ? m_textCursor->selectionEnd() : -1; }
0070     bool selectionContains(int cursorPos) {
0071         if(!m_textCursor)
0072             return false;
0073         return m_textCursor->selectionStart() <= cursorPos && cursorPos < m_textCursor->selectionEnd();
0074     }
0075     bool selectionContainsX(int x) { return selectionContains(xToPos(x)); }
0076 
0077     bool inSelection(int x) const
0078     {
0079         if(!m_textCursor || !m_textCursor->hasSelection())
0080             return false;
0081         int pos = xToPos(x, QTextLine::CursorOnCharacter);
0082         return pos >= m_textCursor->selectionStart() && pos < m_textCursor->selectionEnd();
0083     }
0084 
0085     void eraseSelectedText()
0086     {
0087         removeSelectedText();
0088         finishChange(true);
0089     }
0090 
0091     int start() const { return 0; }
0092     int end() const { return m_document->length(); }
0093 
0094 #ifndef QT_NO_CLIPBOARD
0095     void cut(QClipboard::Mode mode = QClipboard::Clipboard);
0096     void copy(QClipboard::Mode mode = QClipboard::Clipboard) const;
0097     void paste(QClipboard::Mode mode = QClipboard::Clipboard);
0098 #endif
0099 
0100     int cursor() const{ return m_textCursor ? m_textCursor->position() : -1; }
0101 
0102     int cursorWidth() const { return m_cursorWidth; }
0103     void setCursorWidth(int value) { m_cursorWidth = value; }
0104 
0105     void setLineSeparatorSize(const QSizeF &size) { m_layoutSeparatorSize = size; }
0106 
0107     Qt::CursorMoveStyle cursorMoveStyle() const { return m_document->defaultCursorMoveStyle(); }
0108     void setCursorMoveStyle(Qt::CursorMoveStyle style) { m_document->setDefaultCursorMoveStyle(style); }
0109 
0110     void cursorSetPosition(int pos, bool mark=false);
0111     void cursorMoveRelative(QTextCursor::MoveOperation oper, bool mark=false, int n=1);
0112     void cursorMovePosition(int steps, bool mark=false);
0113 
0114     void cursorWordForward(bool mark) { cursorMoveRelative(QTextCursor::NextWord, mark); }
0115     void cursorWordBackward(bool mark) { cursorMoveRelative(QTextCursor::PreviousWord, mark); }
0116 
0117     void home(bool mark);
0118     void end(bool mark);
0119 
0120     int xToPos(int x, QTextLine::CursorPosition = QTextLine::CursorBetweenCharacters) const;
0121     QRect rectForPos(int pos) const;
0122     QRect cursorRect() const;
0123     QRect anchorRect() const;
0124 
0125     qreal cursorToX(int cursor) const;
0126     qreal cursorToX() const { return cursorToX(m_textCursor ? m_textCursor->position() : 0); }
0127 
0128     bool isReadOnly() const { return m_readOnly; }
0129     void setReadOnly(bool enable);
0130 
0131     void setDocument(RichDocument *doc);
0132 
0133     QString text() const;
0134 
0135     void commitPreedit();
0136 
0137     void backspace();
0138     void del();
0139     void deselect() { internalDeselect(); finishChange(false); }
0140     void selectAll();
0141 
0142     enum TextType { Auto, Plain, HTML };
0143     int insert(const QString &html, int pos=-1, TextType textType=Auto);
0144     void clear();
0145     void selectWordAtPos(int);
0146 
0147     QTextCharFormat styleAtPosition(int pos) const;
0148 
0149     void toggleBold();
0150     void toggleItalic();
0151     void toggleUnderline();
0152     void toggleStrikeOut();
0153     const QColor textColor() const;
0154     void setTextColor(const QColor &color);
0155 
0156 #if QT_CONFIG(completer)
0157     QCompleter *completer() const { return m_completer; }
0158     // Note that you must set the widget for the completer separately
0159     void setCompleter(const QCompleter *c) { m_completer = const_cast<QCompleter*>(c); }
0160     void complete(int key);
0161 #endif
0162 
0163     int cursorPosition() const { return m_textCursor ? m_textCursor->position() : -1; }
0164 
0165     // input methods
0166 #ifndef QT_NO_IM
0167     inline bool composeMode() const { return !preeditAreaText().isEmpty(); }
0168     void setPreeditArea(int cursor, const QString &text);
0169 #endif
0170 
0171     int preeditAreaPosition() const;
0172     QString preeditAreaText() const;
0173 
0174     Qt::LayoutDirection layoutDirection() const {
0175         if(m_layoutDirection == Qt::LayoutDirectionAuto && !m_document->isEmpty())
0176             return m_document->toPlainText().isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight;
0177         return m_layoutDirection;
0178     }
0179     void setLayoutDirection(Qt::LayoutDirection direction)
0180     {
0181         if(direction != m_layoutDirection) {
0182             m_layoutDirection = direction;
0183             updateDisplayText();
0184         }
0185     }
0186 
0187     void setFont(const QFont &font);
0188 
0189     void processInputMethodEvent(QInputMethodEvent *event);
0190     void processKeyEvent(QKeyEvent* ev);
0191 
0192     void setBlinkingCursorEnabled(bool enable);
0193     void updateCursorBlinking();
0194 
0195     const QPalette &palette() const { return m_palette; }
0196     void setPalette(const QPalette &p) { m_palette = p; }
0197 
0198     enum DrawFlags {
0199         DrawText = 0x01,
0200         DrawSelections = 0x02,
0201         DrawCursor = 0x04,
0202         DrawAll = DrawText | DrawSelections | DrawCursor
0203     };
0204     void draw(QPainter *, const QPoint &, const QRect &, int flags=DrawAll, int cursorPos=-1);
0205 
0206 #ifndef QT_NO_SHORTCUT
0207     void processShortcutOverrideEvent(QKeyEvent *ke);
0208 #endif
0209 
0210     void updateDisplayText(bool forceUpdate = false);
0211 
0212 private:
0213     void init();
0214 
0215     void removeSelectedText();
0216 
0217     void internalDelete(bool wasBackspace = false);
0218 
0219     inline void internalDeselect()
0220     {
0221         if(m_textCursor) {
0222             m_selDirty |= m_textCursor->hasSelection();
0223             m_textCursor->clearSelection();
0224         }
0225     }
0226 
0227     void emitCursorPositionChanged();
0228 
0229     bool finishChange(bool edited, bool forceUpdate = false);
0230 
0231     QPointer<QCompleter> m_completer;
0232 #if QT_CONFIG(completer)
0233     bool advanceToEnabledItem(int dir);
0234 #endif
0235 
0236     // masking
0237     bool hasAcceptableInput(const QString &text) const;
0238 
0239     virtual void timerEvent(QTimerEvent *event) override;
0240 
0241     QTextLayout * cursorToLayout(int *cursorPosition) const;
0242 
0243 signals:
0244     void cursorPositionChanged(int, int);
0245     void selectionChanged();
0246 
0247     void displayTextChanged(const QString &);
0248     void textChanged(const QString &);
0249     void textEdited(const QString &);
0250 
0251     void resetInputContext();
0252     void updateMicroFocus();
0253 
0254     void accepted();
0255     void editingFinished();
0256     void updateNeeded(const QRect &);
0257     void inputRejected();
0258 
0259 private:
0260     mutable QTextLayout *m_textLayouts = nullptr;
0261     int m_layoutCount = 0;
0262     qreal m_layoutWidth = 0.;
0263     qreal m_layoutHeight = 0.;
0264     QFont m_layoutFont;
0265     QSizeF m_layoutSeparatorSize;
0266 
0267     RichDocument *m_document = nullptr;
0268     QTextCursor *m_textCursor = nullptr;
0269 
0270     QPalette m_palette;
0271     Qt::LayoutDirection m_layoutDirection = Qt::LayoutDirectionAuto;
0272     int m_cursorWidth = 0;
0273 
0274     uint m_readOnly : 1;
0275     uint m_textDirty : 1;
0276     uint m_selDirty : 1;
0277     uint m_blinkStatus : 1;
0278     uint m_blinkEnabled : 1;
0279 
0280     int m_blinkTimer = 0;
0281     int m_ascent = 0;
0282     int m_lastCursorPos = -1;
0283 
0284     int m_keyboardScheme = 0;
0285     QObject *m_accessibleObject = nullptr;
0286 
0287     QVector<QTextLayout::FormatRange> m_preeditRanges;
0288 };
0289 
0290 }
0291 
0292 #endif // RICHDOCUMENTEDITOR_H