- 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:
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user