- Make sure that when adding characters before/after a link that it doesn't take the link style

- Make sure that when double clicking a link with a space the whole text is selected
- Make sure that shift selection with arrows works
- Make sure that ctrl left right (word jump) moves across the whole link even if multiple words
This commit is contained in:
James Graham
2026-02-22 19:17:42 +00:00
parent 0a99e90591
commit d1acb97fe2
8 changed files with 313 additions and 92 deletions

View File

@@ -93,7 +93,6 @@ void ActionsTest::testActions()
auto cache = new ChatBarCache(this); auto cache = new ChatBarCache(this);
cache->cache() += Block::CacheItem{.type = MessageComponentType::Text, .content = QTextDocumentFragment::fromMarkdown(command)}; cache->cache() += Block::CacheItem{.type = MessageComponentType::Text, .content = QTextDocumentFragment::fromMarkdown(command)};
auto result = ActionsModel::handleAction(room, cache); auto result = ActionsModel::handleAction(room, cache);
qWarning() << result << resultText;
QCOMPARE(resultText, std::get<std::optional<QString>>(result)); QCOMPARE(resultText, std::get<std::optional<QString>>(result));
QCOMPARE(type, std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result)); QCOMPARE(type, std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result));
} }

View File

@@ -30,14 +30,14 @@ public:
ChatTextItemHelper *textItem() const ChatTextItemHelper *textItem() const
{ {
return m_keyHelper->textItem; return m_keyHelper->textItem();
} }
void setTextItem(ChatTextItemHelper *textItem) void setTextItem(ChatTextItemHelper *textItem)
{ {
if (textItem == m_keyHelper->textItem) { if (textItem == m_keyHelper->textItem()) {
return; return;
} }
m_keyHelper->textItem = textItem; m_keyHelper->setTextItem(textItem);
Q_EMIT textItemChanged(); Q_EMIT textItemChanged();
} }

View File

@@ -11,13 +11,35 @@
namespace Block namespace Block
{ {
/**
* @struct CacheItem
*
* A structure to define an item stored in a Block::Cache.
*
* @sa Block::Cache
*/
struct CacheItem { struct CacheItem {
MessageComponentType::Type type = MessageComponentType::Other; MessageComponentType::Type type = MessageComponentType::Other;
QTextDocumentFragment content; QTextDocumentFragment content;
/**
* @brief Return the contents of the CacheItem as a single string.
*/
QString toString() const; QString toString() const;
}; };
/**
* @class Cache
*
* A class to cache the contents of a ChatBarMessageContentModel.
*
* We can't store the actual content items as the QTextDocuments are attached to
* text items that may be deleted by the QML engine. Instead we get the contents
* as a QTextDocumentFragment in a Block::CacheItem which can be used to reconstruct the
* model later.
*
* @sa ChatBarMessageContentModel, QTextDocumentFragment, QTextDocument, Block::CacheItem
*/
class Cache : private QList<CacheItem> class Cache : private QList<CacheItem>
{ {
public: public:
@@ -26,8 +48,16 @@ public:
using QList<CacheItem>::clear; using QList<CacheItem>::clear;
using QList<CacheItem>::append, QList<CacheItem>::operator+=, QList<CacheItem>::operator<<; using QList<CacheItem>::append, QList<CacheItem>::operator+=, QList<CacheItem>::operator<<;
/**
* @brief Fill the cache from a list of MessageComponents.
*
* @sa MessageComponent
*/
void fill(QList<MessageComponent> components); void fill(QList<MessageComponent> components);
/**
* @brief Return the contents of the Cache as a single string.
*/
QString toString() const; QString toString() const;
}; };
} }

View File

@@ -7,12 +7,34 @@
#include "clipboard.h" #include "clipboard.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "richformat.h" #include "richformat.h"
#include <qtextcursor.h>
ChatKeyHelper::ChatKeyHelper(QObject *parent) ChatKeyHelper::ChatKeyHelper(QObject *parent)
: QObject(parent) : QObject(parent)
{ {
} }
ChatTextItemHelper *ChatKeyHelper::textItem() const
{
return m_textItem;
}
void ChatKeyHelper::setTextItem(ChatTextItemHelper *textItem)
{
if (textItem == m_textItem) {
return;
}
if (m_textItem) {
m_textItem->disconnect(this);
}
m_textItem = textItem;
if (m_textItem) {
connect(m_textItem, &ChatTextItemHelper::contentsChange, this, &ChatKeyHelper::checkLinkFormat);
connect(m_textItem, &ChatTextItemHelper::selectedTextChanged, this, &ChatKeyHelper::checkMouseSelection);
}
}
bool ChatKeyHelper::handleKey(Qt::Key key, Qt::KeyboardModifiers modifiers) bool ChatKeyHelper::handleKey(Qt::Key key, Qt::KeyboardModifiers modifiers)
{ {
switch (key) { switch (key) {
@@ -23,9 +45,9 @@ bool ChatKeyHelper::handleKey(Qt::Key key, Qt::KeyboardModifiers modifiers)
case Qt::Key_Down: case Qt::Key_Down:
return down(); return down();
case Qt::Key_Left: case Qt::Key_Left:
return left(); return left(modifiers);
case Qt::Key_Right: case Qt::Key_Right:
return right(); return right(modifiers);
case Qt::Key_Tab: case Qt::Key_Tab:
return tab(); return tab();
case Qt::Key_Delete: case Qt::Key_Delete:
@@ -45,10 +67,6 @@ bool ChatKeyHelper::handleKey(Qt::Key key, Qt::KeyboardModifiers modifiers)
bool ChatKeyHelper::vKey(Qt::KeyboardModifiers modifiers) bool ChatKeyHelper::vKey(Qt::KeyboardModifiers modifiers)
{ {
if (!textItem) {
return false;
}
if (modifiers.testFlag(Qt::ControlModifier)) { if (modifiers.testFlag(Qt::ControlModifier)) {
return pasteImage(); return pasteImage();
} }
@@ -57,12 +75,12 @@ bool ChatKeyHelper::vKey(Qt::KeyboardModifiers modifiers)
bool ChatKeyHelper::up(Qt::KeyboardModifiers modifiers) bool ChatKeyHelper::up(Qt::KeyboardModifiers modifiers)
{ {
if (!textItem) { if (!m_textItem) {
return false; return false;
} }
if (modifiers.testFlag(Qt::ControlModifier)) { if (modifiers.testFlag(Qt::ControlModifier)) {
const auto room = textItem->room(); const auto room = m_textItem->room();
if (!room) { if (!room) {
return false; return false;
} }
@@ -70,12 +88,12 @@ bool ChatKeyHelper::up(Qt::KeyboardModifiers modifiers)
return true; return true;
} }
if (textItem->isCompleting) { if (m_textItem->isCompleting) {
Q_EMIT unhandledUp(true); Q_EMIT unhandledUp(true);
return true; return true;
} }
QTextCursor cursor = textItem->textCursor(); QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) { if (cursor.isNull()) {
return false; return false;
} }
@@ -88,15 +106,15 @@ bool ChatKeyHelper::up(Qt::KeyboardModifiers modifiers)
bool ChatKeyHelper::down() bool ChatKeyHelper::down()
{ {
if (!textItem) { if (!m_textItem) {
return false; return false;
} }
if (textItem->isCompleting) { if (m_textItem->isCompleting) {
Q_EMIT unhandledDown(true); Q_EMIT unhandledDown(true);
return true; return true;
} }
QTextCursor cursor = textItem->textCursor(); QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) { if (cursor.isNull()) {
return false; return false;
} }
@@ -108,46 +126,54 @@ bool ChatKeyHelper::down()
return false; return false;
} }
bool ChatKeyHelper::left() bool ChatKeyHelper::left(Qt::KeyboardModifiers modifiers)
{ {
if (!textItem) { if (!m_textItem) {
return false; return false;
} }
QTextCursor cursor = textItem->textCursor(); QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) { if (cursor.isNull()) {
return false; return false;
} }
return selectLink(cursor, true); bool ctrlLeft = nextWordLeft(cursor, modifiers);
if (ctrlLeft) {
return true;
}
return selectLeft(cursor, modifiers);
} }
bool ChatKeyHelper::right() bool ChatKeyHelper::right(Qt::KeyboardModifiers modifiers)
{ {
if (!textItem) { if (!m_textItem) {
return false; return false;
} }
QTextCursor cursor = textItem->textCursor(); QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) { if (cursor.isNull()) {
return false; return false;
} }
return selectLink(cursor, false); bool ctrlRight = nextWordRight(cursor, modifiers);
if (ctrlRight) {
return true;
}
return selectRight(cursor, modifiers);
} }
bool ChatKeyHelper::tab() bool ChatKeyHelper::tab()
{ {
if (!textItem) { if (!m_textItem) {
return false; return false;
} }
if (textItem->isCompleting) { if (m_textItem->isCompleting) {
Q_EMIT unhandledTab(true); Q_EMIT unhandledTab(true);
return true; return true;
} }
QTextCursor cursor = textItem->textCursor(); QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) { if (cursor.isNull()) {
return false; return false;
} }
if (cursor.currentList() && textItem->canIndentListMoreAtCursor()) { if (cursor.currentList() && m_textItem->canIndentListMoreAtCursor()) {
textItem->indentListMoreAtCursor(); m_textItem->indentListMoreAtCursor();
return true; return true;
} }
return false; return false;
@@ -155,45 +181,45 @@ bool ChatKeyHelper::tab()
bool ChatKeyHelper::deleteChar() bool ChatKeyHelper::deleteChar()
{ {
if (!textItem) { if (!m_textItem) {
return false; return false;
} }
QTextCursor cursor = textItem->textCursor(); QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull() || cursor.hasSelection()) { if (cursor.isNull() || cursor.hasSelection()) {
return false; return false;
} }
if (cursor.position() >= textItem->document()->characterCount() - textItem->fixedEndChars().length() - 1) { if (cursor.position() >= m_textItem->document()->characterCount() - m_textItem->fixedEndChars().length() - 1) {
Q_EMIT unhandledDelete(); Q_EMIT unhandledDelete();
return true; return true;
} }
return selectLink(cursor, false); return selectRight(cursor);
} }
bool ChatKeyHelper::backspace() bool ChatKeyHelper::backspace()
{ {
if (!textItem) { if (!m_textItem) {
return false; return false;
} }
QTextCursor cursor = textItem->textCursor(); QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) { if (cursor.isNull()) {
return false; return false;
} }
if (cursor.position() <= textItem->fixedStartChars().length()) { if (cursor.position() <= m_textItem->fixedStartChars().length()) {
if (cursor.currentList() && textItem->canIndentListLessAtCursor()) { if (cursor.currentList() && m_textItem->canIndentListLessAtCursor()) {
textItem->indentListLessAtCursor(); m_textItem->indentListLessAtCursor();
return true; return true;
} }
Q_EMIT unhandledBackspace(); Q_EMIT unhandledBackspace();
return true; return true;
} }
return selectLink(cursor, true); return selectLeft(cursor);
} }
bool ChatKeyHelper::insertReturn(Qt::KeyboardModifiers modifiers) bool ChatKeyHelper::insertReturn(Qt::KeyboardModifiers modifiers)
{ {
if (!textItem) { if (!m_textItem) {
return false; return false;
} }
@@ -203,7 +229,7 @@ bool ChatKeyHelper::insertReturn(Qt::KeyboardModifiers modifiers)
return true; return true;
} }
if (!shiftPressed && textItem->isCompleting) { if (!shiftPressed && m_textItem->isCompleting) {
Q_EMIT unhandledReturn(true); Q_EMIT unhandledReturn(true);
return true; return true;
} }
@@ -213,7 +239,7 @@ bool ChatKeyHelper::insertReturn(Qt::KeyboardModifiers modifiers)
return true; return true;
} }
QTextCursor cursor = textItem->textCursor(); QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) { if (cursor.isNull()) {
return false; return false;
} }
@@ -233,10 +259,10 @@ bool ChatKeyHelper::insertReturn(Qt::KeyboardModifiers modifiers)
bool ChatKeyHelper::cancel() bool ChatKeyHelper::cancel()
{ {
if (!textItem) { if (!m_textItem) {
return false; return false;
} }
if (textItem->isCompleting) { if (m_textItem->isCompleting) {
Q_EMIT closeCompletion(); Q_EMIT closeCompletion();
return true; return true;
} }
@@ -245,7 +271,7 @@ bool ChatKeyHelper::cancel()
bool ChatKeyHelper::pasteImage() bool ChatKeyHelper::pasteImage()
{ {
if (!textItem) { if (!m_textItem) {
return false; return false;
} }
const auto savePath = Clipboard().saveImage(); const auto savePath = Clipboard().saveImage();
@@ -255,50 +281,47 @@ bool ChatKeyHelper::pasteImage()
return false; return false;
} }
bool ChatKeyHelper::selectLink(QTextCursor &cursor, bool back) bool ChatKeyHelper::selectLeft(QTextCursor &cursor, Qt::KeyboardModifiers modifiers)
{ {
if (cursor.hasSelection() || (!cursor.charFormat().isAnchor() && back) || (!back && cursor.atBlockEnd())) { if ((cursor.hasSelection() || !cursor.charFormat().isAnchor()) && !modifiers.testFlag(Qt::ShiftModifier)) {
return false; return false;
} }
// If we are on the very right and going right we need to exit. // We need to rearrange the selection from right to left.
if (!back) { const auto selectionStart = cursor.selectionStart();
const auto startPos = cursor.position(); cursor.setPosition(cursor.selectionEnd());
cursor.movePosition(QTextCursor::NextCharacter); cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, cursor.position() - selectionStart);
if (cursor.charFormat().isAnchor()) {
cursor.setPosition(startPos);
} else {
return false;
}
}
// Figure out if we're on the left side of a link. if (!cursor.charFormat().isAnchor()) {
// note a cusor on the leftmost of a link will not have the anchor set in char format. cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
bool onLeft = false; m_textItem->setSelection(cursor.selectionEnd(), cursor.selectionStart());
if (!cursor.charFormat().isAnchor() && !back) { return true;
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
if (cursor.charFormat().isAnchor()) {
onLeft = true;
} else {
return false;
}
} }
const auto hRef = cursor.charFormat().anchorHref(); const auto hRef = cursor.charFormat().anchorHref();
auto currentCharFormat = cursor.charFormat(); auto currentCharFormat = cursor.charFormat();
while (currentCharFormat.isAnchor() && currentCharFormat.anchorHref() == hRef && cursor.position() > 0) {
// If not on the left figure out where it is. cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
if (!onLeft) { currentCharFormat = cursor.charFormat();
const auto startPos = cursor.position();
while (currentCharFormat.isAnchor() && currentCharFormat.anchorHref() == hRef && cursor.position() > 0) {
cursor.movePosition(QTextCursor::PreviousCharacter);
currentCharFormat = cursor.charFormat();
}
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, startPos - cursor.position());
} }
// Then select everything to right.
// We do it this way so it works when you start in the middle. m_textItem->setSelection(cursor.selectionEnd(), cursor.selectionStart());
currentCharFormat = cursor.charFormat(); return true;
}
bool ChatKeyHelper::selectRight(QTextCursor &cursor, Qt::KeyboardModifiers modifiers)
{
if ((cursor.hasSelection() && !modifiers.testFlag(Qt::ShiftModifier)) || cursor.atBlockEnd()) {
return false;
}
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
if (!cursor.charFormat().isAnchor()) {
return false;
}
const auto hRef = cursor.charFormat().anchorHref();
auto currentCharFormat = cursor.charFormat();
while (currentCharFormat.isAnchor() && currentCharFormat.anchorHref() == hRef && cursor.position() < cursor.block().length() - 1) { while (currentCharFormat.isAnchor() && currentCharFormat.anchorHref() == hRef && cursor.position() < cursor.block().length() - 1) {
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
currentCharFormat = cursor.charFormat(); currentCharFormat = cursor.charFormat();
@@ -307,8 +330,133 @@ bool ChatKeyHelper::selectLink(QTextCursor &cursor, bool back)
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
} }
textItem->setSelection(cursor); m_textItem->setSelection(cursor.selectionStart(), cursor.selectionEnd());
return true; return true;
} }
bool ChatKeyHelper::nextWordLeft(QTextCursor &cursor, Qt::KeyboardModifiers modifiers)
{
if (!modifiers.testFlag(Qt::ControlModifier)) {
return false;
}
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
// Cross any whitespace.
while (cursor.selectedText() == u' ') {
cursor.setPosition(cursor.selectionStart());
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
}
if (!cursor.charFormat().isAnchor()) {
return false;
}
// Cross link.
while (cursor.charFormat().isAnchor()) {
cursor.setPosition(cursor.selectionStart());
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
}
m_textItem->setCursorPosition(cursor.selectionStart());
return true;
}
bool ChatKeyHelper::nextWordRight(QTextCursor &cursor, Qt::KeyboardModifiers modifiers)
{
if (!modifiers.testFlag(Qt::ControlModifier)) {
return false;
}
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
// Cross any whitespace.
while (cursor.selectedText() == u' ') {
cursor.setPosition(cursor.selectionEnd());
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
}
if (!cursor.charFormat().isAnchor()) {
return false;
}
// Cross link.
while (cursor.charFormat().isAnchor()) {
cursor.setPosition(cursor.selectionEnd());
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
}
m_textItem->setCursorPosition(cursor.selectionEnd());
return true;
}
void ChatKeyHelper::checkMouseSelection()
{
if (!m_textItem) {
return;
}
QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) {
return;
}
if (!cursor.hasSelection()) {
return;
}
bool selectingLink = false;
cursor.beginEditBlock();
cursor.setPosition(m_textItem->selectionStart());
if (cursor.charFormat().isAnchor()) {
selectingLink = true;
}
if (!selectingLink) {
cursor.movePosition(QTextCursor::NextCharacter);
if (cursor.charFormat().isAnchor()) {
selectingLink = true;
}
}
if (!selectingLink) {
cursor.setPosition(m_textItem->selectionEnd());
if (cursor.charFormat().isAnchor()) {
selectingLink = true;
}
}
if (!selectingLink) {
return;
}
// Wind all the way to the left of the link.
cursor.setPosition(m_textItem->selectionStart());
const auto hRef = cursor.charFormat().anchorHref();
auto currentCharFormat = cursor.charFormat();
while (currentCharFormat.isAnchor() && currentCharFormat.anchorHref() == hRef && cursor.position() > 0) {
cursor.movePosition(QTextCursor::PreviousCharacter);
currentCharFormat = cursor.charFormat();
}
cursor.endEditBlock();
selectRight(cursor);
}
void ChatKeyHelper::checkLinkFormat(int position, int charsRemoved, int charsAdded)
{
if (!m_textItem || charsRemoved > charsAdded || charsAdded - charsRemoved != 1) {
return;
}
QTextCursor cursor = m_textItem->textCursor();
if (cursor.isNull()) {
return;
}
bool nextToLink = false;
cursor.setPosition(position);
if (cursor.charFormat().isAnchor()) {
nextToLink = true;
}
// Note 2 because a cursor on the left of a link will not show it in the format.
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, 2);
if (cursor.charFormat().isAnchor()) {
nextToLink = true;
}
if (!nextToLink) {
return;
}
cursor.beginEditBlock();
cursor.setPosition(position);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
cursor.setCharFormat({});
cursor.endEditBlock();
}
#include "moc_chatkeyhelper.cpp" #include "moc_chatkeyhelper.cpp"

View File

@@ -32,7 +32,14 @@ public:
* *
* @sa ChatTextItemHelper * @sa ChatTextItemHelper
*/ */
QPointer<ChatTextItemHelper> textItem; ChatTextItemHelper *textItem() const;
/**
* @brief Set the ChatTextItemHelper that ChatKeyHelper is handling key presses for.
*
* @sa ChatTextItemHelper
*/
void setTextItem(ChatTextItemHelper *textItem);
/** /**
* @brief handle the given key and modifiers. * @brief handle the given key and modifiers.
@@ -115,15 +122,17 @@ Q_SIGNALS:
void imagePasted(const QString &filePath); void imagePasted(const QString &filePath);
private: private:
QPointer<ChatTextItemHelper> m_textItem;
bool vKey(Qt::KeyboardModifiers modifiers); bool vKey(Qt::KeyboardModifiers modifiers);
bool up(Qt::KeyboardModifiers modifiers); bool up(Qt::KeyboardModifiers modifiers);
bool down(); bool down();
bool left(); bool left(Qt::KeyboardModifiers modifiers);
bool right(); bool right(Qt::KeyboardModifiers modifiers);
bool tab(); bool tab();
@@ -137,5 +146,15 @@ private:
bool pasteImage(); bool pasteImage();
bool selectLink(QTextCursor &cursor, bool back); bool selectLeft(QTextCursor &cursor, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
bool selectRight(QTextCursor &cursor, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
bool nextWordLeft(QTextCursor &cursor, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
bool nextWordRight(QTextCursor &cursor, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
void checkMouseSelection();
void checkLinkFormat(int position, int charsRemoved, int charsAdded);
}; };

View File

@@ -82,6 +82,7 @@ void ChatTextItemHelper::setTextItem(QQuickItem *textItem)
if (m_textItem) { if (m_textItem) {
connect(m_textItem, SIGNAL(cursorPositionChanged()), this, SLOT(itemCursorPositionChanged())); connect(m_textItem, SIGNAL(cursorPositionChanged()), this, SLOT(itemCursorPositionChanged()));
connect(m_textItem, SIGNAL(selectedTextChanged()), this, SLOT(itemSelectedTextChanged()));
connect(m_textItem, SIGNAL(textFormatChanged(QQuickTextEdit::TextFormat)), this, SLOT(itemTextFormatChanged())); connect(m_textItem, SIGNAL(textFormatChanged(QQuickTextEdit::TextFormat)), this, SLOT(itemTextFormatChanged()));
if (const auto doc = document()) { if (const auto doc = document()) {
connect(doc, &QTextDocument::contentsChanged, this, &ChatTextItemHelper::contentsChanged); connect(doc, &QTextDocument::contentsChanged, this, &ChatTextItemHelper::contentsChanged);
@@ -387,12 +388,13 @@ void ChatTextItemHelper::setCursorPosition(int pos)
m_textItem->setProperty("cursorPosition", pos); m_textItem->setProperty("cursorPosition", pos);
} }
void ChatTextItemHelper::setSelection(const QTextCursor &cursor) void ChatTextItemHelper::setSelection(int selectionStart, int selectionEnd)
{ {
if (!m_textItem) { if (!m_textItem) {
return; return;
} }
metaObject()->invokeMethod(m_textItem, "select", Qt::DirectConnection, cursor.selectionStart(), cursor.selectionEnd()); m_selectionJustChanged = true;
metaObject()->invokeMethod(m_textItem, "select", Qt::DirectConnection, selectionStart, selectionEnd);
} }
void ChatTextItemHelper::setCursorVisible(bool visible) void ChatTextItemHelper::setCursorVisible(bool visible)
@@ -448,6 +450,15 @@ void ChatTextItemHelper::itemCursorPositionChanged()
Q_EMIT listChanged(); Q_EMIT listChanged();
} }
void ChatTextItemHelper::itemSelectedTextChanged()
{
if (m_selectionJustChanged) {
m_selectionJustChanged = false;
return;
}
Q_EMIT selectedTextChanged();
}
void ChatTextItemHelper::mergeFormatOnCursor(RichFormat::Format format, QTextCursor cursor) void ChatTextItemHelper::mergeFormatOnCursor(RichFormat::Format format, QTextCursor cursor)
{ {
if (cursor.isNull()) { if (cursor.isNull()) {

View File

@@ -182,6 +182,16 @@ public:
*/ */
QRect cursorRectangle() const; QRect cursorRectangle() const;
/**
* @brief The start position of any text selection in the underlying text item.
*/
int selectionStart() const;
/**
* @brief The end position of any text selection in the underlying text item.
*/
int selectionEnd() const;
/** /**
* @brief Set the cursor position of the underlying text item to the given value. * @brief Set the cursor position of the underlying text item to the given value.
*/ */
@@ -190,7 +200,7 @@ public:
/** /**
* @brief Set the selection of the underlying text item to the given cursor. * @brief Set the selection of the underlying text item to the given cursor.
*/ */
void setSelection(const QTextCursor &cursor); void setSelection(int selectionStart, int selectionEnd);
/** /**
* @brief Set the cursor visibility of the underlying text item to the given value. * @brief Set the cursor visibility of the underlying text item to the given value.
@@ -312,11 +322,17 @@ Q_SIGNALS:
*/ */
void cursorPositionChanged(bool fromContentsChange); void cursorPositionChanged(bool fromContentsChange);
/**
* @brief Emitted when the selected text of the underlying text item is changed.
*/
void selectedTextChanged();
private: private:
QPointer<QQuickItem> m_textItem; QPointer<QQuickItem> m_textItem;
QPointer<ChatBarSyntaxHighlighter> m_highlighter; QPointer<ChatBarSyntaxHighlighter> m_highlighter;
bool m_contentsJustChanged = false; bool m_contentsJustChanged = false;
bool m_selectionJustChanged = false;
QString m_fixedStartChars = {}; QString m_fixedStartChars = {};
QString m_fixedEndChars = {}; QString m_fixedEndChars = {};
@@ -326,9 +342,6 @@ private:
std::optional<int> lineLength(int lineNumber) const; std::optional<int> lineLength(int lineNumber) const;
int selectionStart() const;
int selectionEnd() const;
void mergeTextFormatOnCursor(RichFormat::Format format, QTextCursor cursor); void mergeTextFormatOnCursor(RichFormat::Format format, QTextCursor cursor);
void mergeStyleFormatOnCursor(RichFormat::Format format, QTextCursor cursor); void mergeStyleFormatOnCursor(RichFormat::Format format, QTextCursor cursor);
void mergeListFormatOnCursor(RichFormat::Format format, const QTextCursor &cursor); void mergeListFormatOnCursor(RichFormat::Format format, const QTextCursor &cursor);
@@ -339,4 +352,5 @@ private:
private Q_SLOTS: private Q_SLOTS:
void itemTextFormatChanged(); void itemTextFormatChanged();
void itemCursorPositionChanged(); void itemCursorPositionChanged();
void itemSelectedTextChanged();
}; };

View File

@@ -38,7 +38,7 @@ ChatBarMessageContentModel::ChatBarMessageContentModel(QObject *parent)
}); });
connect(this, &ChatBarMessageContentModel::focusRowChanged, this, [this]() { connect(this, &ChatBarMessageContentModel::focusRowChanged, this, [this]() {
m_markdownHelper->setTextItem(focusedTextItem()); m_markdownHelper->setTextItem(focusedTextItem());
m_keyHelper->textItem = focusedTextItem(); m_keyHelper->setTextItem(focusedTextItem());
}); });
connect(this, &ChatBarMessageContentModel::roomChanged, this, [this]() { connect(this, &ChatBarMessageContentModel::roomChanged, this, [this]() {
for (const auto &component : m_components) { for (const auto &component : m_components) {