Add automatic markdown formatting
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <Sonnet/BackgroundChecker>
|
||||
#include <Sonnet/Settings>
|
||||
#include <qfont.h>
|
||||
#include <qlogging.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qtextcursor.h>
|
||||
@@ -28,8 +29,8 @@
|
||||
|
||||
#include "chatbartype.h"
|
||||
#include "chatdocumenthandler_logging.h"
|
||||
#include "chatmarkdownhelper.h"
|
||||
#include "eventhandler.h"
|
||||
#include "textstyle.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
@@ -130,6 +131,8 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
|
||||
, m_highlighter(new SyntaxHighlighter(this))
|
||||
, m_completionModel(new CompletionModel(this))
|
||||
{
|
||||
m_markdownHelper = new ChatMarkdownHelper(this);
|
||||
connect(this, &ChatDocumentHandler::formatChanged, m_markdownHelper, &ChatMarkdownHelper::handleExternalFormatChange);
|
||||
}
|
||||
|
||||
int ChatDocumentHandler::completionStartIndex() const
|
||||
@@ -162,6 +165,22 @@ void ChatDocumentHandler::setType(ChatBarType::Type type)
|
||||
Q_EMIT typeChanged();
|
||||
}
|
||||
|
||||
NeoChatRoom *ChatDocumentHandler::room() const
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (m_room == room) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_room = room;
|
||||
m_completionModel->setRoom(m_room);
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
QQuickItem *ChatDocumentHandler::textItem() const
|
||||
{
|
||||
return m_textItem;
|
||||
@@ -187,9 +206,17 @@ void ChatDocumentHandler::setTextItem(QQuickItem *textItem)
|
||||
connect(m_textItem, SIGNAL(cursorPositionChanged()), this, SLOT(updateCursor()));
|
||||
if (document()) {
|
||||
connect(document(), &QTextDocument::contentsChanged, this, &ChatDocumentHandler::contentsChanged);
|
||||
connect(document(), &QTextDocument::contentsChanged, this, [this]() {
|
||||
if (m_room) {
|
||||
updateCursor();
|
||||
connect(document(), &QTextDocument::contentsChanged, this, &ChatDocumentHandler::updateCursor);
|
||||
connect(document(), &QTextDocument::contentsChange, this, [this](int position) {
|
||||
auto cursor = textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return;
|
||||
}
|
||||
cursor.setPosition(position);
|
||||
cursor.select(QTextCursor::WordUnderCursor);
|
||||
if (!cursor.selectedText().isEmpty()) {
|
||||
cursor.mergeCharFormat(m_pendingFormat);
|
||||
m_pendingFormat = {};
|
||||
}
|
||||
});
|
||||
initializeChars();
|
||||
@@ -308,7 +335,6 @@ void ChatDocumentHandler::updateCursor()
|
||||
int start = completionStartIndex();
|
||||
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
|
||||
|
||||
Q_EMIT formatChanged();
|
||||
Q_EMIT atFirstLineChanged();
|
||||
Q_EMIT atLastLineChanged();
|
||||
}
|
||||
@@ -329,20 +355,20 @@ int ChatDocumentHandler::selectionEnd() const
|
||||
return m_textItem->property("selectionEnd").toInt();
|
||||
}
|
||||
|
||||
NeoChatRoom *ChatDocumentHandler::room() const
|
||||
QTextCursor ChatDocumentHandler::textCursor() const
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (m_room == room) {
|
||||
return;
|
||||
if (!document()) {
|
||||
return QTextCursor();
|
||||
}
|
||||
|
||||
m_room = room;
|
||||
m_completionModel->setRoom(m_room);
|
||||
Q_EMIT roomChanged();
|
||||
QTextCursor cursor = QTextCursor(document());
|
||||
if (selectionStart() != selectionEnd()) {
|
||||
cursor.setPosition(selectionStart());
|
||||
cursor.setPosition(selectionEnd(), QTextCursor::KeepAnchor);
|
||||
} else {
|
||||
cursor.setPosition(cursorPosition());
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
bool ChatDocumentHandler::isEmpty() const
|
||||
@@ -647,35 +673,6 @@ void ChatDocumentHandler::updateMentions(const QString &editId)
|
||||
}
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setFontSize(int size)
|
||||
{
|
||||
if (size <= 0)
|
||||
return;
|
||||
|
||||
QTextCursor cursor = textCursor();
|
||||
if (cursor.isNull())
|
||||
return;
|
||||
|
||||
if (!cursor.hasSelection())
|
||||
cursor.select(QTextCursor::WordUnderCursor);
|
||||
|
||||
if (cursor.charFormat().property(QTextFormat::FontPointSize).toInt() == size)
|
||||
return;
|
||||
|
||||
QTextCharFormat format;
|
||||
format.setFontPointSize(size);
|
||||
mergeFormatOnWordOrSelection(format);
|
||||
Q_EMIT fontSizeChanged();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setStrikethrough(bool strikethrough)
|
||||
{
|
||||
QTextCharFormat format;
|
||||
format.setFontStrikeOut(strikethrough);
|
||||
mergeFormatOnWordOrSelection(format);
|
||||
Q_EMIT formatChanged();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setTextColor(const QColor &color)
|
||||
{
|
||||
QTextCharFormat format;
|
||||
@@ -684,23 +681,6 @@ void ChatDocumentHandler::setTextColor(const QColor &color)
|
||||
Q_EMIT textColorChanged();
|
||||
}
|
||||
|
||||
Qt::Alignment ChatDocumentHandler::alignment() const
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
if (cursor.isNull())
|
||||
return Qt::AlignLeft;
|
||||
return textCursor().blockFormat().alignment();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setAlignment(Qt::Alignment alignment)
|
||||
{
|
||||
QTextBlockFormat format;
|
||||
format.setAlignment(alignment);
|
||||
QTextCursor cursor = textCursor();
|
||||
cursor.mergeBlockFormat(format);
|
||||
Q_EMIT alignmentChanged();
|
||||
}
|
||||
|
||||
bool ChatDocumentHandler::bold() const
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
@@ -710,14 +690,6 @@ bool ChatDocumentHandler::bold() const
|
||||
return textCursor().charFormat().fontWeight() == QFont::Bold;
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setBold(bool bold)
|
||||
{
|
||||
QTextCharFormat format;
|
||||
format.setFontWeight(bold ? QFont::Bold : QFont::Normal);
|
||||
mergeFormatOnWordOrSelection(format);
|
||||
Q_EMIT formatChanged();
|
||||
}
|
||||
|
||||
bool ChatDocumentHandler::italic() const
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
@@ -726,14 +698,6 @@ bool ChatDocumentHandler::italic() const
|
||||
return textCursor().charFormat().fontItalic();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setItalic(bool italic)
|
||||
{
|
||||
QTextCharFormat format;
|
||||
format.setFontItalic(italic);
|
||||
mergeFormatOnWordOrSelection(format);
|
||||
Q_EMIT formatChanged();
|
||||
}
|
||||
|
||||
bool ChatDocumentHandler::underline() const
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
@@ -742,14 +706,6 @@ bool ChatDocumentHandler::underline() const
|
||||
return textCursor().charFormat().fontUnderline();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setUnderline(bool underline)
|
||||
{
|
||||
QTextCharFormat format;
|
||||
format.setFontUnderline(underline);
|
||||
mergeFormatOnWordOrSelection(format);
|
||||
Q_EMIT formatChanged();
|
||||
}
|
||||
|
||||
bool ChatDocumentHandler::strikethrough() const
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
@@ -758,23 +714,6 @@ bool ChatDocumentHandler::strikethrough() const
|
||||
return textCursor().charFormat().fontStrikeOut();
|
||||
}
|
||||
|
||||
QString ChatDocumentHandler::fontFamily() const
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
if (cursor.isNull())
|
||||
return QString();
|
||||
QTextCharFormat format = cursor.charFormat();
|
||||
return format.font().family();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setFontFamily(const QString &family)
|
||||
{
|
||||
QTextCharFormat format;
|
||||
format.setFontFamilies({family});
|
||||
mergeFormatOnWordOrSelection(format);
|
||||
Q_EMIT fontFamilyChanged();
|
||||
}
|
||||
|
||||
QColor ChatDocumentHandler::textColor() const
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
@@ -784,22 +723,6 @@ QColor ChatDocumentHandler::textColor() const
|
||||
return format.foreground().color();
|
||||
}
|
||||
|
||||
QTextCursor ChatDocumentHandler::textCursor() const
|
||||
{
|
||||
if (!document()) {
|
||||
return QTextCursor();
|
||||
}
|
||||
|
||||
QTextCursor cursor = QTextCursor(document());
|
||||
if (selectionStart() != selectionEnd()) {
|
||||
cursor.setPosition(selectionStart());
|
||||
cursor.setPosition(selectionEnd(), QTextCursor::KeepAnchor);
|
||||
} else {
|
||||
cursor.setPosition(cursorPosition());
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
std::optional<Qt::TextFormat> ChatDocumentHandler::textFormat() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
@@ -812,9 +735,14 @@ std::optional<Qt::TextFormat> ChatDocumentHandler::textFormat() const
|
||||
void ChatDocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
if (!cursor.hasSelection())
|
||||
if (!cursor.hasSelection()) {
|
||||
cursor.select(QTextCursor::WordUnderCursor);
|
||||
cursor.mergeCharFormat(format);
|
||||
}
|
||||
if (cursor.hasSelection()) {
|
||||
cursor.mergeCharFormat(format);
|
||||
} else {
|
||||
m_pendingFormat = format.toCharFormat();
|
||||
}
|
||||
}
|
||||
|
||||
QString ChatDocumentHandler::currentLinkText() const
|
||||
@@ -993,19 +921,38 @@ void ChatDocumentHandler::regenerateColorScheme()
|
||||
// TODO update existing link
|
||||
}
|
||||
|
||||
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:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::indentListMore()
|
||||
{
|
||||
m_nestedListHelper.handleOnIndentMore(textCursor());
|
||||
Q_EMIT currentListStyleChanged();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::indentListLess()
|
||||
{
|
||||
m_nestedListHelper.handleOnIndentLess(textCursor());
|
||||
Q_EMIT currentListStyleChanged();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setListStyle(int styleIndex)
|
||||
void ChatDocumentHandler::setListFormat(RichFormat::Format format)
|
||||
{
|
||||
m_nestedListHelper.handleOnBulletType(-styleIndex, textCursor());
|
||||
m_nestedListHelper.handleOnBulletType(RichFormat::listStyleForFormat(format), textCursor());
|
||||
Q_EMIT currentListStyleChanged();
|
||||
}
|
||||
|
||||
@@ -1028,28 +975,38 @@ int ChatDocumentHandler::currentListStyle() const
|
||||
return -textCursor().currentList()->format().style();
|
||||
}
|
||||
|
||||
TextStyle::Style ChatDocumentHandler::style() const
|
||||
void ChatDocumentHandler::setTextFormat(RichFormat::Format format)
|
||||
{
|
||||
return static_cast<TextStyle::Style>(textCursor().blockFormat().headingLevel());
|
||||
if (RichFormat::typeForFormat(format) != RichFormat::Text) {
|
||||
return;
|
||||
}
|
||||
mergeFormatOnWordOrSelection(RichFormat::charFormatForFormat(format, RichFormat::hasFormat(textCursor(), format)));
|
||||
Q_EMIT formatChanged();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setStyle(TextStyle::Style style)
|
||||
RichFormat::Format ChatDocumentHandler::style() const
|
||||
{
|
||||
const int headingLevel = style <= 6 ? style : 0;
|
||||
// Apparently, 5 is maximum for FontSizeAdjustment; otherwise level=1 and
|
||||
// level=2 look the same
|
||||
const int sizeAdjustment = headingLevel > 0 ? 5 - headingLevel : 0;
|
||||
return static_cast<RichFormat::Format>(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;
|
||||
}
|
||||
|
||||
qWarning() << format;
|
||||
|
||||
QTextCursor cursor = textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return;
|
||||
}
|
||||
cursor.beginEditBlock();
|
||||
|
||||
QTextBlockFormat blkfmt;
|
||||
blkfmt.setHeadingLevel(headingLevel);
|
||||
cursor.mergeBlockFormat(blkfmt);
|
||||
cursor.mergeBlockFormat(RichFormat::blockFormatForFormat(format));
|
||||
|
||||
QTextCharFormat chrfmt;
|
||||
chrfmt.setFontWeight(headingLevel > 0 ? QFont::Bold : QFont::Normal);
|
||||
chrfmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment);
|
||||
// Applying style to the current line or selection
|
||||
QTextCursor selectCursor = cursor;
|
||||
if (selectCursor.hasSelection()) {
|
||||
@@ -1066,23 +1023,16 @@ void ChatDocumentHandler::setStyle(TextStyle::Style style)
|
||||
} else {
|
||||
selectCursor.select(QTextCursor::BlockUnderCursor);
|
||||
}
|
||||
selectCursor.mergeCharFormat(chrfmt);
|
||||
|
||||
const auto chrfmt = RichFormat::charFormatForFormat(format);
|
||||
selectCursor.mergeCharFormat(chrfmt);
|
||||
cursor.mergeBlockCharFormat(chrfmt);
|
||||
cursor.endEditBlock();
|
||||
|
||||
Q_EMIT formatChanged();
|
||||
Q_EMIT styleChanged();
|
||||
}
|
||||
|
||||
int ChatDocumentHandler::fontSize() const
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
if (cursor.isNull())
|
||||
return 0;
|
||||
QTextCharFormat format = cursor.charFormat();
|
||||
return format.font().pointSize();
|
||||
}
|
||||
|
||||
QString ChatDocumentHandler::fileName() const
|
||||
{
|
||||
const QString filePath = QQmlFile::urlToLocalFileOrQrc(m_fileUrl);
|
||||
@@ -1102,6 +1052,19 @@ QUrl ChatDocumentHandler::fileUrl() const
|
||||
return m_fileUrl;
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::tab()
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return;
|
||||
}
|
||||
if (cursor.currentList()) {
|
||||
indentListMore();
|
||||
return;
|
||||
}
|
||||
insertText(u" "_s);
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::deleteChar()
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
@@ -1124,6 +1087,11 @@ void ChatDocumentHandler::backspace()
|
||||
return;
|
||||
}
|
||||
if (cursor.position() <= m_fixedStartChars.length()) {
|
||||
qWarning() << "unhandled backspace";
|
||||
if (cursor.currentList()) {
|
||||
indentListLess();
|
||||
return;
|
||||
}
|
||||
if (const auto previousHandler = previousDocumentHandler()) {
|
||||
previousHandler->insertFragment(takeFirstBlock(), End, true);
|
||||
} else {
|
||||
@@ -1134,6 +1102,15 @@ void ChatDocumentHandler::backspace()
|
||||
cursor.deletePreviousChar();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::insertReturn()
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return;
|
||||
}
|
||||
cursor.insertBlock();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::insertText(const QString &text)
|
||||
{
|
||||
textCursor().insertText(text);
|
||||
|
||||
Reference in New Issue
Block a user