[network/ruqola] src/widgets: Fix bug for selecting text (fixed selected text bottom to top which has
Laurent Montel <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 5c6747803e2770687e3ab4ff422fcb01952b85ae by Laurent Montel.
Committed on 26/07/2026 at 21:33.
Pushed by mlaurent into branch 'master'.
Fix bug for selecting text (fixed selected text bottom to top which has
some attachment url)
M +35 -0 src/widgets/delegateutils/textselection.cpp
M +7 -0 src/widgets/delegateutils/textselection.h
M +47 -7 src/widgets/room/autotests/textselectiontest.cpp
M +1 -0 src/widgets/room/autotests/textselectiontest.h
https://invent.kde.org/network/ruqola/-/commit/5c6747803e2770687e3ab4ff422fcb01952b85ae
diff --git a/src/widgets/delegateutils/textselection.cpp b/src/widgets/delegateutils/textselection.cpp
index bd3a45c1d8..1a20e88854 100644
--- a/src/widgets/delegateutils/textselection.cpp
+++ b/src/widgets/delegateutils/textselection.cpp
@@ -228,6 +228,35 @@ QTextCursor TextSelection::selectionForIndex(const QModelIndex &index, QTextDocu
}
}
+ if ((att.isValid() || msgUrl.hasHtmlDescription()) && mStartPos >= 0 && ordered.fromRow != ordered.toRow && fromCharPos == toCharPos) {
+ // If selection started in message text and now spans multiple rows,
+ // keep URL/attachment block visibly selected instead of a collapsed point.
+ fromCharPos = 0;
+ toCharPos = maxCharPos;
+ }
+
+ if (att.isValid() || msgUrl.hasHtmlDescription()) {
+ // Attachment/URL preview selection is always local to that document;
+ // don't reinterpret it through multi-row text boundaries.
+ cursor.setPosition(qBound(0, fromCharPos, maxCharPos));
+ cursor.setPosition(qBound(0, toCharPos, maxCharPos), QTextCursor::KeepAnchor);
+ return cursor;
+ }
+
+ if (mStartPos >= 0 && mEndSelectionArea != EndSelectionArea::Text && mStartIndex.isValid() && mEndIndex.isValid() && mStartIndex.row() != mEndIndex.row()
+ && index.row() == mStartIndex.row()) {
+ // Keep the original text-side selection on the start row while the endpoint is in URL/attachment.
+ cursor.setPosition(qBound(0, mStartPos, maxCharPos));
+ cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
+ return cursor;
+ }
+
+ if (ordered.fromRow != ordered.toRow && mEndIndex.isValid() && index.row() == mEndIndex.row() && mEndSelectionArea != EndSelectionArea::Text) {
+ // Mouse endpoint is currently in attachment/URL preview on this row;
+ // don't select this row's main message text until endpoint enters text.
+ return {};
+ }
+
// qDebug() << "AFTER toCharPos" << toCharPos << " fromCharPos " << fromCharPos;
const int row = index.row();
if (row == ordered.fromRow)
@@ -254,6 +283,7 @@ void TextSelection::clear()
mEndIndex = QPersistentModelIndex{};
mStartPos = -1;
mEndPos = -1;
+ mEndSelectionArea = EndSelectionArea::Text;
mAttachmentSelection.clear();
mMessageUrlSelection.clear();
@@ -327,6 +357,7 @@ void TextSelection::setTextSelectionEnd(const QModelIndex &index, int charPos)
Q_ASSERT(index.isValid());
mEndIndex = index;
mEndPos = charPos;
+ mEndSelectionArea = EndSelectionArea::Text;
}
void TextSelection::setAttachmentTextSelectionEnd(const QModelIndex &index, int charPos, const MessageAttachment &msgAttach)
@@ -340,6 +371,7 @@ void TextSelection::setAttachmentTextSelectionEnd(const QModelIndex &index, int
// The drag endpoint is currently in attachment text; main text must stay unselected.
mEndPos = -1;
}
+ mEndSelectionArea = EndSelectionArea::Attachment;
if (msgAttach.isValid()) {
const auto countAtt{mAttachmentSelection.count()};
for (int i = 0; i < countAtt; ++i) {
@@ -352,6 +384,7 @@ void TextSelection::setAttachmentTextSelectionEnd(const QModelIndex &index, int
}
AttachmentSelection selection;
+ selection.fromCharPos = keepTextSelectionEndPos ? charPos : 0;
selection.toCharPos = charPos;
selection.attachment = msgAttach;
mAttachmentSelection.append(std::move(selection));
@@ -369,6 +402,7 @@ void TextSelection::setPreviewUrlTextSelectionEnd(const QModelIndex &index, int
// The drag endpoint is currently in URL preview text; main text must stay unselected.
mEndPos = -1;
}
+ mEndSelectionArea = EndSelectionArea::MessageUrl;
if (msgUrl.hasHtmlDescription()) {
const auto countMessageUrl{mMessageUrlSelection.count()};
for (int i = 0; i < countMessageUrl; ++i) {
@@ -381,6 +415,7 @@ void TextSelection::setPreviewUrlTextSelectionEnd(const QModelIndex &index, int
}
MessageUrlSelection selection;
+ selection.fromCharPos = keepTextSelectionEndPos ? charPos : 0;
selection.toCharPos = charPos;
selection.messageUrl = msgUrl;
mMessageUrlSelection.append(std::move(selection));
diff --git a/src/widgets/delegateutils/textselection.h b/src/widgets/delegateutils/textselection.h
index 930fe0ee0c..9caf95a2a0 100644
--- a/src/widgets/delegateutils/textselection.h
+++ b/src/widgets/delegateutils/textselection.h
@@ -113,6 +113,12 @@ private:
int toCharPos = 0;
};
+ enum class EndSelectionArea : uint8_t {
+ Text,
+ Attachment,
+ MessageUrl,
+ };
+
[[nodiscard]] LIBRUQOLAWIDGETS_NO_EXPORT OrderedPositions orderedPositions() const;
LIBRUQOLAWIDGETS_NO_EXPORT void selectionText(const OrderedPositions ordered,
Format format,
@@ -129,6 +135,7 @@ private:
QList<MessageUrlSelection> mMessageUrlSelection;
int mStartPos = -1; // first selected character in start row
int mEndPos = -1; // last selected character in end row
+ EndSelectionArea mEndSelectionArea = EndSelectionArea::Text;
DocumentFactoryInterface *mTextHelperFactory = nullptr;
DocumentFactoryInterface *mMessageUrlHelperFactory = nullptr;
diff --git a/src/widgets/room/autotests/textselectiontest.cpp b/src/widgets/room/autotests/textselectiontest.cpp
index e9eb021467..9885482256 100644
--- a/src/widgets/room/autotests/textselectiontest.cpp
+++ b/src/widgets/room/autotests/textselectiontest.cpp
@@ -275,6 +275,45 @@ void TextSelectionTest::testSelectionExtendingToUrlPreviewKeepsTextSelection()
const QTextCursor urlCursor = selection.selectionForIndex(index1, &urlPreviewDoc, {}, messageUrl);
QVERIFY(!urlCursor.isNull());
+ QCOMPARE(urlCursor.position(), urlCursor.anchor());
+}
+
+void TextSelectionTest::testSelectionFromTextToUrlDoesNotSelectUrlRowTextUntilTextIsHit()
+{
+ const QModelIndex index1 = model.index(1, 0);
+ const QModelIndex index2 = model.index(2, 0);
+ TestFactory factory(model.rowCount());
+ TextSelection selection;
+ selection.setTextHelperFactory(&factory);
+
+ MessageUrl messageUrl;
+ messageUrl.setUrl(u"https://kde.org"_s);
+ messageUrl.setPageTitle(u"KDE"_s);
+ messageUrl.setDescription(u"Community"_s);
+ messageUrl.generateMessageUrlInfo();
+ QVERIFY(messageUrl.hasHtmlDescription());
+
+ // Start from message text in a lower row.
+ selection.setTextSelectionStart(index2, 0);
+ selection.setTextSelectionEnd(index2, 11);
+
+ // Move endpoint up into URL preview (same row as index1 message).
+ selection.setPreviewUrlTextSelectionEnd(index1, 2, messageUrl);
+ selection.setPreviewUrlTextSelectionEnd(index1, 12, messageUrl);
+
+ const QTextCursor row2Cursor = selection.selectionForIndex(index2, factory.documentForIndex(index2));
+ QVERIFY(!row2Cursor.isNull());
+ QCOMPARE(row2Cursor.selection().toPlainText(), u"Line 2 bold"_s);
+
+ // While endpoint is in URL preview on row 1, row 1 message text must stay unselected.
+ const QTextCursor row1Cursor = selection.selectionForIndex(index1, factory.documentForIndex(index1));
+ QVERIFY(row1Cursor.isNull() || row1Cursor.selection().toPlainText().isEmpty());
+
+ QTextDocument urlPreviewDoc;
+ urlPreviewDoc.setHtml(messageUrl.htmlDescription());
+ const QTextCursor urlCursor = selection.selectionForIndex(index1, &urlPreviewDoc, {}, messageUrl);
+ QVERIFY(!urlCursor.isNull());
+ QVERIFY(!urlCursor.selection().toPlainText().isEmpty());
}
void TextSelectionTest::testSelectionStartingInUrlPreviewAndMovingToText()
@@ -312,7 +351,7 @@ void TextSelectionTest::testSelectionStartingInUrlPreviewAndMovingToText()
void TextSelectionTest::testSelectionStartingInUrlPreviewAndMovingToPreviousMessage()
{
const QModelIndex index0 = model.index(0, 0);
- const QModelIndex index1 = model.index(1, 0);
+ const QModelIndex index2 = model.index(2, 0);
TestFactory factory(model.rowCount());
TextSelection selection;
selection.setTextHelperFactory(&factory);
@@ -324,22 +363,23 @@ void TextSelectionTest::testSelectionStartingInUrlPreviewAndMovingToPreviousMess
messageUrl.generateMessageUrlInfo();
QVERIFY(messageUrl.hasHtmlDescription());
- selection.setPreviewUrlTextSelectionStart(index1, 366, messageUrl);
+ selection.setPreviewUrlTextSelectionStart(index2, 2, messageUrl);
+ selection.setPreviewUrlTextSelectionEnd(index2, 12, messageUrl);
- // Move the selection endpoint to the previous message row.
+ // Move the selection endpoint two rows up.
selection.setTextSelectionEnd(index0, 4);
const QTextCursor row0Cursor = selection.selectionForIndex(index0, factory.documentForIndex(index0));
QVERIFY(!row0Cursor.isNull());
QCOMPARE(row0Cursor.selection().toPlainText(), u" 0"_s);
- const QTextCursor row1Cursor = selection.selectionForIndex(index1, factory.documentForIndex(index1));
- QVERIFY(!row1Cursor.isNull());
- QCOMPARE(row1Cursor.selection().toPlainText(), u"Line 1 bold"_s);
+ const QTextCursor row2Cursor = selection.selectionForIndex(index2, factory.documentForIndex(index2));
+ QVERIFY(!row2Cursor.isNull());
+ QCOMPARE(row2Cursor.selection().toPlainText(), u"Line 2 bold"_s);
QTextDocument urlPreviewDoc;
urlPreviewDoc.setHtml(messageUrl.htmlDescription());
- const QTextCursor urlCursor = selection.selectionForIndex(index1, &urlPreviewDoc, {}, messageUrl);
+ const QTextCursor urlCursor = selection.selectionForIndex(index2, &urlPreviewDoc, {}, messageUrl);
QVERIFY(!urlCursor.isNull());
QVERIFY(!urlCursor.selection().toPlainText().isEmpty());
}
diff --git a/src/widgets/room/autotests/textselectiontest.h b/src/widgets/room/autotests/textselectiontest.h
index daea08e110..0e3fdba6b9 100644
--- a/src/widgets/room/autotests/textselectiontest.h
+++ b/src/widgets/room/autotests/textselectiontest.h
@@ -25,6 +25,7 @@ private Q_SLOTS:
void testSelectAll();
void testSelectionForIndexDoesNotIncludeUrlPreviewByDefault();
void testSelectionExtendingToUrlPreviewKeepsTextSelection();
+ void testSelectionFromTextToUrlDoesNotSelectUrlRowTextUntilTextIsHit();
void testSelectionStartingInUrlPreviewAndMovingToText();
void testSelectionStartingInUrlPreviewAndMovingToPreviousMessage();
void textClear();