Add tests for ChatMarkdownHelper and rework how formats are applied to make it more robust.

This commit is contained in:
James Graham
2025-12-27 13:24:26 +00:00
parent f31e9062e6
commit 45163944d0
13 changed files with 463 additions and 167 deletions

View File

@@ -136,6 +136,8 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
, m_markdownHelper(new ChatMarkdownHelper(this))
, m_highlighter(new SyntaxHighlighter(this))
{
connectTextItem();
connect(this, &ChatDocumentHandler::formatChanged, m_markdownHelper, &ChatMarkdownHelper::handleExternalFormatChange);
}
@@ -176,6 +178,7 @@ QQuickItem *ChatDocumentHandler::textItem() const
void ChatDocumentHandler::setTextItem(QQuickItem *textItem)
{
m_textItem->setTextItem(textItem);
m_markdownHelper->setTextItem(textItem);
}
void ChatDocumentHandler::connectTextItem()
@@ -209,6 +212,10 @@ void ChatDocumentHandler::connectTextItem()
}
}
});
connect(m_textItem, &QmlTextItemWrapper::formatChanged, this, &ChatDocumentHandler::formatChanged);
connect(m_textItem, &QmlTextItemWrapper::textFormatChanged, this, &ChatDocumentHandler::textFormatChanged);
connect(m_textItem, &QmlTextItemWrapper::styleChanged, this, &ChatDocumentHandler::styleChanged);
connect(m_textItem, &QmlTextItemWrapper::listChanged, this, &ChatDocumentHandler::listChanged);
}
ChatDocumentHandler *ChatDocumentHandler::previousDocumentHandler() const
@@ -756,65 +763,36 @@ void ChatDocumentHandler::regenerateColorScheme()
void ChatDocumentHandler::setFormat(RichFormat::Format format)
{
switch (RichFormat::typeForFormat(format)) {
case RichFormat::Text:
setTextFormat(format);
return;
case RichFormat::List:
setListFormat(format);
return;
case RichFormat::Style:
setStyleFormat(format);
return;
default:
QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) {
return;
}
}
void ChatDocumentHandler::indentListMore()
{
m_nestedListHelper.handleOnIndentMore(m_textItem->textCursor());
Q_EMIT currentListStyleChanged();
}
void ChatDocumentHandler::indentListLess()
{
m_nestedListHelper.handleOnIndentLess(m_textItem->textCursor());
Q_EMIT currentListStyleChanged();
}
void ChatDocumentHandler::setListFormat(RichFormat::Format format)
{
m_nestedListHelper.handleOnBulletType(RichFormat::listStyleForFormat(format), m_textItem->textCursor());
Q_EMIT currentListStyleChanged();
}
bool ChatDocumentHandler::canIndentList() const
{
return m_nestedListHelper.canIndent(m_textItem->textCursor()) && m_textItem->textCursor().blockFormat().headingLevel() == 0;
}
bool ChatDocumentHandler::canDedentList() const
{
return m_nestedListHelper.canDedent(m_textItem->textCursor()) && m_textItem->textCursor().blockFormat().headingLevel() == 0;
m_textItem->mergeFormatOnCursor(format, cursor);
}
int ChatDocumentHandler::currentListStyle() const
{
if (!m_textItem->textCursor().currentList()) {
return 0;
}
return -m_textItem->textCursor().currentList()->format().style();
return m_textItem->currentListStyle();
}
void ChatDocumentHandler::setTextFormat(RichFormat::Format format)
bool ChatDocumentHandler::canIndentListMore() const
{
if (RichFormat::typeForFormat(format) != RichFormat::Text) {
return;
}
mergeFormatOnWordOrSelection(RichFormat::charFormatForFormat(format, RichFormat::hasFormat(m_textItem->textCursor(), format)));
Q_EMIT formatChanged();
return m_textItem->canIndentListMore();
}
bool ChatDocumentHandler::canIndentListLess() const
{
return m_textItem->canIndentListLess();
}
void ChatDocumentHandler::indentListMore()
{
m_textItem->indentListMore();
}
void ChatDocumentHandler::indentListLess()
{
m_textItem->indentListLess();
}
RichFormat::Format ChatDocumentHandler::style() const
@@ -822,48 +800,6 @@ RichFormat::Format ChatDocumentHandler::style() const
return static_cast<RichFormat::Format>(m_textItem->textCursor().blockFormat().headingLevel());
}
void ChatDocumentHandler::setStyleFormat(RichFormat::Format format)
{
// Paragraph is special because it is normally a Block format but if we're already
// in a Paragraph it clears any existing style.
if (!(RichFormat::typeForFormat(format) == RichFormat::Style || format == RichFormat::Paragraph)) {
return;
}
QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) {
return;
}
cursor.beginEditBlock();
cursor.mergeBlockFormat(RichFormat::blockFormatForFormat(format));
// Applying style to the current line or selection
QTextCursor selectCursor = cursor;
if (selectCursor.hasSelection()) {
QTextCursor top = selectCursor;
top.setPosition(qMin(top.anchor(), top.position()));
top.movePosition(QTextCursor::StartOfBlock);
QTextCursor bottom = selectCursor;
bottom.setPosition(qMax(bottom.anchor(), bottom.position()));
bottom.movePosition(QTextCursor::EndOfBlock);
selectCursor.setPosition(top.position(), QTextCursor::MoveAnchor);
selectCursor.setPosition(bottom.position(), QTextCursor::KeepAnchor);
} else {
selectCursor.select(QTextCursor::BlockUnderCursor);
}
const auto chrfmt = RichFormat::charFormatForFormat(format);
selectCursor.mergeCharFormat(chrfmt);
cursor.mergeBlockCharFormat(chrfmt);
cursor.endEditBlock();
Q_EMIT formatChanged();
Q_EMIT styleChanged();
}
void ChatDocumentHandler::tab()
{
QTextCursor cursor = m_textItem->textCursor();

View File

@@ -97,14 +97,14 @@ class ChatDocumentHandler : public QObject
Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor NOTIFY textColorChanged)
Q_PROPERTY(bool bold READ bold NOTIFY formatChanged)
Q_PROPERTY(bool italic READ italic NOTIFY formatChanged)
Q_PROPERTY(bool underline READ underline NOTIFY formatChanged)
Q_PROPERTY(bool strikethrough READ strikethrough NOTIFY formatChanged)
Q_PROPERTY(bool bold READ bold NOTIFY textFormatChanged)
Q_PROPERTY(bool italic READ italic NOTIFY textFormatChanged)
Q_PROPERTY(bool underline READ underline NOTIFY textFormatChanged)
Q_PROPERTY(bool strikethrough READ strikethrough NOTIFY textFormatChanged)
Q_PROPERTY(RichFormat::Format style READ style NOTIFY styleChanged)
Q_PROPERTY(int currentListStyle READ currentListStyle NOTIFY currentListStyleChanged)
Q_PROPERTY(int currentListStyle READ currentListStyle NOTIFY listChanged)
public:
enum InsertPosition {
@@ -161,11 +161,10 @@ public:
bool strikethrough() const;
Q_INVOKABLE void setFormat(RichFormat::Format format);
void setFormatOnCursor(RichFormat::Format format, const QTextCursor &cursor);
bool canIndentList() const;
bool canDedentList() const;
int currentListStyle() const;
bool canIndentListMore() const;
bool canIndentListLess() const;
Q_INVOKABLE void indentListLess();
Q_INVOKABLE void indentListMore();
@@ -195,11 +194,12 @@ Q_SIGNALS:
void textColorChanged();
void checkableChanged();
void currentListStyleChanged();
void formatChanged();
void textFormatChanged();
void styleChanged();
void listChanged();
void contentsChanged();
@@ -220,10 +220,6 @@ private:
QString m_initialText = {};
void initializeChars();
void setTextFormat(RichFormat::Format format);
void setStyleFormat(RichFormat::Format format);
void setListFormat(RichFormat::Format format);
QPointer<ChatMarkdownHelper> m_markdownHelper;
std::optional<QTextCharFormat> m_pendingFormat = std::nullopt;
std::optional<QTextCharFormat> m_pendingOverrideFormat = std::nullopt;
@@ -236,7 +232,6 @@ private:
std::optional<Qt::TextFormat> textFormat() const;
void mergeFormatOnWordOrSelection(const QTextCharFormat &format);
void selectLinkText(QTextCursor *cursor) const;
NestedListHelper m_nestedListHelper;
QColor linkColor();
QColor mLinkColor;
void regenerateColorScheme();

View File

@@ -3,11 +3,15 @@
#include "chatmarkdownhelper.h"
#include <QTextCursor>
#include <QTextDocument>
#include <qtextcursor.h>
#include "chatdocumenthandler.h"
#include "qmltextitemwrapper.h"
#include "richformat.h"
namespace
{
struct MarkdownSyntax {
QLatin1String sequence;
bool closable = false;
@@ -15,7 +19,7 @@ struct MarkdownSyntax {
RichFormat::Format format;
};
static const QList<MarkdownSyntax> syntax = {
const QList<MarkdownSyntax> syntax = {
MarkdownSyntax{.sequence = "*"_L1, .closable = true, .format = RichFormat::Italic},
MarkdownSyntax{.sequence = "**"_L1, .closable = true, .format = RichFormat::Bold},
MarkdownSyntax{.sequence = "# "_L1, .lineStart = true, .format = RichFormat::Heading1},
@@ -35,7 +39,7 @@ static const QList<MarkdownSyntax> syntax = {
MarkdownSyntax{.sequence = "__"_L1, .closable = true, .format = RichFormat::Underline},
};
static std::optional<bool> checkSequence(const QString &currentString, const QString &nextChar, bool lineStart = false)
std::optional<bool> checkSequence(const QString &currentString, const QString &nextChar, bool lineStart = false)
{
QList<MarkdownSyntax> partialMatches;
std::optional<MarkdownSyntax> fullMatch = std::nullopt;
@@ -65,7 +69,7 @@ static std::optional<bool> checkSequence(const QString &currentString, const QSt
return std::nullopt;
}
static std::optional<MarkdownSyntax> syntaxForSequence(const QString &sequence)
std::optional<MarkdownSyntax> syntaxForSequence(const QString &sequence)
{
const auto it = std::find_if(syntax.cbegin(), syntax.cend(), [sequence](const MarkdownSyntax &syntax) {
return syntax.sequence == sequence;
@@ -75,55 +79,41 @@ static std::optional<MarkdownSyntax> syntaxForSequence(const QString &sequence)
}
return *it;
}
}
ChatMarkdownHelper::ChatMarkdownHelper(ChatDocumentHandler *parent)
ChatMarkdownHelper::ChatMarkdownHelper(QObject *parent)
: QObject(parent)
, m_textItem(new QmlTextItemWrapper(this))
{
Q_ASSERT(parent);
connectDocument();
connect(parent, &ChatDocumentHandler::textItemChanged, this, &ChatMarkdownHelper::connectDocument);
connectTextItem();
}
QTextDocument *ChatMarkdownHelper::document() const
QQuickItem *ChatMarkdownHelper::textItem() const
{
const auto documentHandler = qobject_cast<ChatDocumentHandler *>(parent());
if (!documentHandler) {
return nullptr;
}
if (!documentHandler->textItem()) {
return nullptr;
}
const auto quickDocument = qvariant_cast<QQuickTextDocument *>(documentHandler->textItem()->property("textDocument"));
return quickDocument ? quickDocument->textDocument() : nullptr;
return m_textItem->textItem();
}
void ChatMarkdownHelper::connectDocument()
void ChatMarkdownHelper::setTextItem(QQuickItem *textItem)
{
disconnect();
m_textItem->setTextItem(textItem);
}
if (document()) {
m_startPos = qobject_cast<ChatDocumentHandler *>(parent())->textItem()->property("cursorPosition").toInt();
void ChatMarkdownHelper::connectTextItem()
{
connect(m_textItem, &QmlTextItemWrapper::textItemChanged, this, &ChatMarkdownHelper::textItemChanged);
connect(m_textItem, &QmlTextItemWrapper::textItemChanged, this, [this]() {
m_startPos = m_textItem->cursorPosition();
m_endPos = m_startPos;
if (m_startPos == 0) {
m_currentState = Pre;
}
connect(document(), &QTextDocument::contentsChange, this, &ChatMarkdownHelper::checkMarkdown);
}
});
connect(m_textItem, &QmlTextItemWrapper::textDocumentContentsChange, this, &ChatMarkdownHelper::checkMarkdown);
}
void ChatMarkdownHelper::checkMarkdown(int position, int charsRemoved, int charsAdded)
{
qWarning() << "1" << m_currentState << m_startPos << m_endPos;
if (!document()) {
return;
}
auto cursor = QTextCursor(document());
auto cursor = m_textItem->textCursor();
if (cursor.isNull()) {
return;
}
@@ -137,7 +127,6 @@ void ChatMarkdownHelper::checkMarkdown(int position, int charsRemoved, int chars
cursor.setPosition(m_endPos + (cursor.atBlockEnd() ? 0 : 1), QTextCursor::KeepAnchor);
const auto nextChar = cursor.selectedText();
m_currentState = m_startPos == 0 || nextChar == u' ' ? Pre : None;
qWarning() << "2" << m_currentState << m_startPos << m_endPos;
return;
}
@@ -151,7 +140,7 @@ void ChatMarkdownHelper::checkMarkdown(int position, int charsRemoved, int chars
cursor.setPosition(m_startPos);
const auto result = checkSequence(currentMarkdown, nextChar, cursor.atBlockStart());
qWarning() << result;
qWarning() << m_startPos << m_endPos << result;
switch (m_currentState) {
case None:
@@ -186,16 +175,15 @@ void ChatMarkdownHelper::checkMarkdown(int position, int charsRemoved, int chars
break;
}
}
qWarning() << "2" << m_currentState << m_startPos << m_endPos;
}
void ChatMarkdownHelper::complete()
{
auto cursor = QTextCursor(document());
auto cursor = m_textItem->textCursor();
if (cursor.isNull()) {
return;
}
cursor.beginEditBlock();
cursor.setPosition(m_startPos);
cursor.setPosition(m_endPos, QTextCursor::KeepAnchor);
const auto syntax = syntaxForSequence(cursor.selectedText());
@@ -207,29 +195,38 @@ void ChatMarkdownHelper::complete()
m_currentFormats.insert(syntax->format, m_startPos);
}
++m_startPos;
cursor.setPosition(m_startPos);
cursor.setPosition(m_startPos + 1, QTextCursor::KeepAnchor);
const auto nextChar = cursor.selectedText();
const auto result = checkSequence({}, nextChar, cursor.atBlockStart());
m_currentState = result ? Started : Pre;
const auto documentHandler = qobject_cast<ChatDocumentHandler *>(parent());
// cursor.setPosition(m_startPos + 1);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
if (syntax) {
documentHandler->textItem()->setProperty("cursorPosition", m_startPos);
documentHandler->setFormat(syntax->format);
const auto formatType = RichFormat::typeForFormat(syntax->format);
if (formatType == RichFormat::Block) {
Q_EMIT unhandledBlockFormat(syntax->format);
} else {
m_textItem->mergeFormatOnCursor(syntax->format, cursor);
}
}
m_currentState = Pre;
m_endPos = m_startPos;
m_startPos = result ? m_startPos : m_startPos + 1;
m_endPos = result ? m_startPos + 1 : m_startPos;
documentHandler->textItem()->setProperty("cursorPosition", m_startPos);
cursor.endEditBlock();
qWarning() << m_currentState << m_startPos << m_endPos << m_textItem->cursorPosition();
}
void ChatMarkdownHelper::handleExternalFormatChange()
{
auto cursor = QTextCursor(document());
auto cursor = m_textItem->textCursor();
if (cursor.isNull()) {
return;
}
cursor.setPosition(m_startPos);
m_currentState = RichFormat::formatsAtCursor(cursor).length() > 0 ? Pre : None;
qWarning() << "3" << m_currentState << m_startPos << m_endPos;
}
#include "moc_chatmarkdownhelper.cpp"

View File

@@ -4,24 +4,30 @@
#pragma once
#include <QObject>
#include <QQmlEngine>
#include "enums/richformat.h"
class QQuickItem;
class QTextDocument;
class ChatDocumentHandler;
class QmlTextItemWrapper;
class ChatMarkdownHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
public:
explicit ChatMarkdownHelper(ChatDocumentHandler *parent);
explicit ChatMarkdownHelper(QObject *parent = nullptr);
QQuickItem *textItem() const;
void setTextItem(QQuickItem *textItem);
void handleExternalFormatChange();
Q_SIGNALS:
void textItemChanged();
void unhandledBlockFormat(RichFormat::Format format);
private:
enum State {
None,
@@ -29,8 +35,8 @@ private:
Started,
};
QTextDocument *document() const;
void connectDocument();
QPointer<QmlTextItemWrapper> m_textItem;
void connectTextItem();
State m_currentState = None;
int m_startPos = 0;

View File

@@ -6,7 +6,6 @@
#include <QTextBlockFormat>
#include <QTextCharFormat>
#include <QTextCursor>
#include <qtextformat.h>
QString RichFormat::styleString(Format format)
{
@@ -68,7 +67,7 @@ QTextListFormat::Style RichFormat::listStyleForFormat(Format format)
}
}
QTextCharFormat RichFormat::charFormatForFormat(Format format, bool invert)
QTextCharFormat RichFormat::charFormatForFormat(Format format, bool invert, const QColor &highlightColor)
{
QTextCharFormat charFormat;
if (format == Bold || headingLevelForFormat(format) > 0) {
@@ -84,6 +83,16 @@ QTextCharFormat RichFormat::charFormatForFormat(Format format, bool invert)
if (format == Strikethrough) {
charFormat.setFontStrikeOut(!invert);
}
if (format == InlineCode) {
if (invert) {
charFormat.setFont({});
charFormat.setBackground({});
} else {
charFormat.setFontFamilies({u"monospace"_s});
charFormat.setFontFixedPitch(!invert);
charFormat.setBackground(highlightColor);
}
}
if (headingLevelForFormat(format) > 0) {
// Apparently, 4 is maximum for FontSizeAdjustment; otherwise level=1 and
// level=2 look the same
@@ -144,6 +153,8 @@ bool RichFormat::hasFormat(QTextCursor cursor, Format format)
return cursor.charFormat().fontStrikeOut();
case Underline:
return cursor.charFormat().fontUnderline();
case InlineCode:
return cursor.charFormat().fontFixedPitch();
default:
return false;
}
@@ -167,6 +178,9 @@ QList<RichFormat::Format> RichFormat::formatsAtCursor(const QTextCursor &cursor)
if (cursor.charFormat().fontStrikeOut()) {
formats += Strikethrough;
}
if (cursor.charFormat().fontFixedPitch()) {
formats += InlineCode;
}
if (cursor.blockFormat().headingLevel() > 0 && cursor.blockFormat().headingLevel() <= 6) {
formats += formatForHeadingLevel(cursor.blockFormat().headingLevel());
}

View File

@@ -91,7 +91,7 @@ public:
*
* @sa Format, QTextCharFormat
*/
static QTextCharFormat charFormatForFormat(Format format, bool invert = false);
static QTextCharFormat charFormatForFormat(Format format, bool invert = false, const QColor &highlightColor = {});
/**
* @brief Return the QTextBlockFormat for the Format.

View File

@@ -6,6 +6,8 @@
#include <QQuickTextDocument>
#include <QTextCursor>
#include <Kirigami/Platform/PlatformTheme>
QmlTextItemWrapper::QmlTextItemWrapper(QObject *parent)
: QObject(parent)
{
@@ -112,6 +114,133 @@ void QmlTextItemWrapper::textDocCursorChanged()
Q_EMIT textDocumentCursorPositionChanged();
}
void QmlTextItemWrapper::mergeFormatOnCursor(RichFormat::Format format, const QTextCursor &cursor)
{
if (cursor.isNull()) {
return;
}
switch (RichFormat::typeForFormat(format)) {
case RichFormat::Text:
mergeTextFormatOnCursor(format, cursor);
return;
case RichFormat::List:
mergeListFormatOnCursor(format, cursor);
return;
case RichFormat::Style:
mergeStyleFormatOnCursor(format, cursor);
return;
default:
return;
}
}
void QmlTextItemWrapper::mergeTextFormatOnCursor(RichFormat::Format format, QTextCursor cursor)
{
if (RichFormat::typeForFormat(format) != RichFormat::Text) {
return;
}
const auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
const auto charFormat = RichFormat::charFormatForFormat(format, RichFormat::hasFormat(cursor, format), theme->alternateBackgroundColor());
if (!cursor.hasSelection()) {
cursor.select(QTextCursor::WordUnderCursor);
}
cursor.mergeCharFormat(charFormat);
Q_EMIT formatChanged();
Q_EMIT textFormatChanged();
}
void QmlTextItemWrapper::mergeStyleFormatOnCursor(RichFormat::Format format, QTextCursor cursor)
{
// Paragraph is special because it is normally a Block format but if we're already
// in a Paragraph it clears any existing style.
if (!(RichFormat::typeForFormat(format) == RichFormat::Style || format == RichFormat::Paragraph)) {
return;
}
cursor.beginEditBlock();
cursor.mergeBlockFormat(RichFormat::blockFormatForFormat(format));
// Applying style to the current line or selection
QTextCursor selectCursor = cursor;
if (selectCursor.hasSelection()) {
QTextCursor top = selectCursor;
top.setPosition(qMin(top.anchor(), top.position()));
top.movePosition(QTextCursor::StartOfBlock);
QTextCursor bottom = selectCursor;
bottom.setPosition(qMax(bottom.anchor(), bottom.position()));
bottom.movePosition(QTextCursor::EndOfBlock);
selectCursor.setPosition(top.position(), QTextCursor::MoveAnchor);
selectCursor.setPosition(bottom.position(), QTextCursor::KeepAnchor);
} else {
selectCursor.select(QTextCursor::BlockUnderCursor);
}
const auto chrfmt = RichFormat::charFormatForFormat(format);
selectCursor.mergeCharFormat(chrfmt);
cursor.mergeBlockCharFormat(chrfmt);
cursor.endEditBlock();
Q_EMIT formatChanged();
Q_EMIT styleChanged();
}
void QmlTextItemWrapper::mergeListFormatOnCursor(RichFormat::Format format, const QTextCursor &cursor)
{
m_nestedListHelper.handleOnBulletType(RichFormat::listStyleForFormat(format), cursor);
Q_EMIT formatChanged();
Q_EMIT listChanged();
}
int QmlTextItemWrapper::currentListStyle() const
{
auto cursor = textCursor();
if (cursor.isNull() || !textCursor().currentList()) {
return 0;
}
return -cursor.currentList()->format().style();
}
bool QmlTextItemWrapper::canIndentListMore() const
{
auto cursor = textCursor();
if (cursor.isNull()) {
return false;
}
return m_nestedListHelper.canIndent(cursor) && cursor.blockFormat().headingLevel() == 0;
}
bool QmlTextItemWrapper::canIndentListLess() const
{
auto cursor = textCursor();
if (cursor.isNull()) {
return false;
}
return m_nestedListHelper.canDedent(cursor) && cursor.blockFormat().headingLevel() == 0;
}
void QmlTextItemWrapper::indentListMore()
{
auto cursor = textCursor();
if (cursor.isNull()) {
return;
}
m_nestedListHelper.handleOnIndentMore(cursor);
Q_EMIT listChanged();
}
void QmlTextItemWrapper::indentListLess()
{
auto cursor = textCursor();
if (cursor.isNull()) {
return;
}
m_nestedListHelper.handleOnIndentLess(cursor);
Q_EMIT listChanged();
}
void QmlTextItemWrapper::forceActiveFocus() const
{
if (!m_textItem) {

View File

@@ -6,12 +6,15 @@
#include <QObject>
#include <QQuickItem>
#include "enums/richformat.h"
#include "nestedlisthelper_p.h"
class QTextDocument;
/**
* @class QmlTextItemWrapper
*
* A class to wrap around a QQuickItem that is a QML TextEdit (or inherited from it) and provide easy acess to its properties.
* A class to wrap around a QQuickItem that is a QML TextEdit (or inherited from it).
*
* @note This basically exists because Qt does not give us access to the cpp headers of
* most QML items.
@@ -31,9 +34,18 @@ public:
QTextDocument *document() const;
QTextCursor textCursor() const;
int cursorPosition() const;
void setCursorPosition(int pos);
void setCursorVisible(bool visible);
void mergeFormatOnCursor(RichFormat::Format format, const QTextCursor &cursor);
int currentListStyle() const;
bool canIndentListMore() const;
bool canIndentListLess() const;
void indentListMore();
void indentListLess();
void forceActiveFocus() const;
Q_SIGNALS:
@@ -45,13 +57,22 @@ Q_SIGNALS:
void textDocumentCursorPositionChanged();
void formatChanged();
void textFormatChanged();
void styleChanged();
void listChanged();
private:
QPointer<QQuickItem> m_textItem;
int cursorPosition() const;
int selectionStart() const;
int selectionEnd() const;
void mergeTextFormatOnCursor(RichFormat::Format format, QTextCursor cursor);
void mergeStyleFormatOnCursor(RichFormat::Format format, QTextCursor cursor);
void mergeListFormatOnCursor(RichFormat::Format format, const QTextCursor &cursor);
NestedListHelper m_nestedListHelper;
private Q_SLOTS:
void textDocCursorChanged();
};