aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2023-11-02 20:10:40 -0700
committerOliver Eftevaag <oliver.eftevaag@qt.io>2023-12-08 20:11:28 +0100
commit045f9ce192d841f3cc36d514b5f238b46488b41e (patch)
tree2dddf5bea40d39f4e197c51f203887638ac4c08a
parent0a1313d8e75e805f6dff86d8b682cb6f9bf3a7bb (diff)
Add TextSelection (Tech Preview)
In the Controls text editor example, DocumentHandler always sounded like a hack, just by its name. We don't expect to be able to handle multiple selections anytime soon; but if we realistically expect to have multi-seat support in Qt some day, then probably the multi-user experience should include support for multiple text cursors and selections. So we shouldn't paint ourselves into a corner. QQuickTextControl works with only one QTextCursor most of the time (but it's private, thus modifiable); and TextEdit has properties like selectionStart, selectionEnd, selectedText, etc. which seem to assume that there is only one selection. So probably if we needed to support multiple selections, we could add Q_PROPERTY(QQmlListProperty<QQuickTextSelection> selections ...), document that those legacy properties just work with the first selection, and/or deprecate them. So with that in mind, let's get started with a QQuickTextSelection object. We add TextEdit.cursorSelection which holds the single selection near the text cursor. It provides API needed for tracking and manipulating often-used properties of selected rich text (such as QTextCharFormat properties) so that DocumentHandler can be removed. The example now uses TextArea.cursorSelection to manipulate the selected text's format. It's not possible to be fully declarative with this API though; we need to call setFont (by assigning a font), but QFont is a value type, and is not as mergeable as QTextCharFormat is, for example. If we used a binding rather than Action.onTriggered, it would trigger reading the font for an entire span of selected text (which may have had multiple fonts), setting one attribute (like bold), then applying the font to the whole span. What we do now is almost like that; but instead of reading the font first, we start with a default-constructed QFont, set one attribute, and call QTextCursor::mergeCharFormat(), in the hope that it can merge only the features of QFont that have actually been set. Unfortunately this is not quite true either: if you toggle the bold button, it might change the font size too, and so on; so maybe we really need QTextCharFormat in QML (as a value type, presumably) to implement those feature-toggling toolbar buttons correctly. This API is in tech preview, because of such issues as described above; because we're just scratching the surface of what might be possible; because we should perhaps compare popular JavaScript text-editing APIs that might be found elsewhere, in the meantime get feedback from users during the tech preview phase, and keep iterating. [ChangeLog][QtQuick][TextEdit] TextEdit.cursorSelection is a TextSelection object, which provides properties to inspect and modify the formatting of the single selection that is currently supported. This API is in Tech Preview. [ChangeLog][Controls][TextArea] TextArea.cursorSelection is a TextSelection object, which provides properties to inspect and modify the formatting of the single selection that is currently supported. This API is in Tech Preview. Task-number: QTBUG-36521 Task-number: QTBUG-38830 Task-number: QTBUG-81022 Change-Id: Icea99f633694aa712d0b4730b77369077288540f Reviewed-by: Oliver Eftevaag <oliver.eftevaag@qt.io>
-rw-r--r--examples/quickcontrols/texteditor/qml/texteditor.qml149
-rw-r--r--src/quick/CMakeLists.txt1
-rw-r--r--src/quick/items/qquicktextedit.cpp19
-rw-r--r--src/quick/items/qquicktextedit_p.h4
-rw-r--r--src/quick/items/qquicktextedit_p_p.h3
-rw-r--r--src/quick/util/qquicktextselection.cpp188
-rw-r--r--src/quick/util/qquicktextselection_p.h81
-rw-r--r--tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp69
8 files changed, 463 insertions, 51 deletions
diff --git a/examples/quickcontrols/texteditor/qml/texteditor.qml b/examples/quickcontrols/texteditor/qml/texteditor.qml
index c6a53d8c1b..03f987fc5b 100644
--- a/examples/quickcontrols/texteditor/qml/texteditor.qml
+++ b/examples/quickcontrols/texteditor/qml/texteditor.qml
@@ -76,19 +76,64 @@ ApplicationWindow {
Action {
id: boldAction
shortcut: StandardKey.Bold
- onTriggered: document.bold = !document.bold
+ checkable: true
+ checked: textArea.cursorSelection.font.bold
+ onTriggered: textArea.cursorSelection.font = Qt.font({ bold: checked })
}
Action {
id: italicAction
shortcut: StandardKey.Italic
- onTriggered: document.italic = !document.italic
+ checkable: true
+ checked: textArea.cursorSelection.font.italic
+ onTriggered: textArea.cursorSelection.font = Qt.font({ italic: checked })
}
Action {
id: underlineAction
shortcut: StandardKey.Underline
- onTriggered: document.underline = !document.underline
+ checkable: true
+ checked: textArea.cursorSelection.font.underline
+ onTriggered: textArea.cursorSelection.font = Qt.font({ underline: checked })
+ }
+
+ Action {
+ id: strikeoutAction
+ checkable: true
+ checked: textArea.cursorSelection.font.strikeout
+ onTriggered: textArea.cursorSelection.font = Qt.font({ strikeout: checked })
+ }
+
+ Action {
+ id: alignLeftAction
+ shortcut: "Ctrl+{"
+ checkable: true
+ checked: textArea.cursorSelection.alignment === Qt.AlignLeft
+ onTriggered: textArea.cursorSelection.alignment = Qt.AlignLeft
+ }
+
+ Action {
+ id: alignCenterAction
+ shortcut: "Ctrl+|"
+ checkable: true
+ checked: textArea.cursorSelection.alignment === Qt.AlignCenter
+ onTriggered: textArea.cursorSelection.alignment = Qt.AlignCenter
+ }
+
+ Action {
+ id: alignRightAction
+ shortcut: "Ctrl+}"
+ checkable: true
+ checked: textArea.cursorSelection.alignment === Qt.AlignRight
+ onTriggered: textArea.cursorSelection.alignment = Qt.AlignRight
+ }
+
+ Action {
+ id: alignJustifyAction
+ shortcut: "Ctrl+Alt+}"
+ checkable: true
+ checked: textArea.cursorSelection.alignment === Qt.AlignJustify
+ onTriggered: textArea.cursorSelection.alignment = Qt.AlignJustify
}
Platform.MenuBar {
@@ -135,26 +180,53 @@ ApplicationWindow {
Platform.MenuItem {
text: qsTr("&Bold")
checkable: true
- checked: document.bold
- onTriggered: document.bold = !document.bold
+ checked: boldAction.checked
+ onTriggered: boldAction.trigger()
}
Platform.MenuItem {
text: qsTr("&Italic")
checkable: true
- checked: document.italic
- onTriggered: document.italic = !document.italic
+ checked: italicAction.checked
+ onTriggered: italicAction.trigger()
}
Platform.MenuItem {
text: qsTr("&Underline")
checkable: true
- checked: document.underline
- onTriggered: document.underline = !document.underline
+ checked: underlineAction.checked
+ onTriggered: underlineAction.trigger()
}
Platform.MenuItem {
text: qsTr("&Strikeout")
checkable: true
- checked: document.strikeout
- onTriggered: document.strikeout = !document.strikeout
+ checked: strikeoutAction.checked
+ onTriggered: strikeoutAction.trigger()
+ }
+
+ Platform.MenuSeparator {}
+
+ Platform.MenuItem {
+ text: qsTr("Align &Left")
+ checkable: true
+ checked: alignLeftAction.checked
+ onTriggered: alignLeftAction.trigger()
+ }
+ Platform.MenuItem {
+ text: qsTr("&Center")
+ checkable: true
+ checked: alignCenterAction.checked
+ onTriggered: alignCenterAction.trigger()
+ }
+ Platform.MenuItem {
+ text: qsTr("&Justify")
+ checkable: true
+ checked: alignJustifyAction.checked
+ onTriggered: alignJustifyAction.trigger()
+ }
+ Platform.MenuItem {
+ text: qsTr("Align &Right")
+ checkable: true
+ checked: alignRightAction.checked
+ onTriggered: alignRightAction.trigger()
}
}
}
@@ -181,13 +253,13 @@ ApplicationWindow {
FontDialog {
id: fontDialog
- onAccepted: document.font = selectedFont
+ onAccepted: textArea.cursorSelection.font = selectedFont
}
ColorDialog {
id: colorDialog
selectedColor: "black"
- onAccepted: document.textColor = selectedColor
+ onAccepted: textArea.cursorSelection.color = selectedColor
}
MessageDialog {
@@ -280,8 +352,6 @@ ApplicationWindow {
text: "\uE800" // icon-bold
font.family: "fontello"
focusPolicy: Qt.TabFocus
- checkable: true
- checked: document.bold
action: boldAction
}
ToolButton {
@@ -289,8 +359,6 @@ ApplicationWindow {
text: "\uE801" // icon-italic
font.family: "fontello"
focusPolicy: Qt.TabFocus
- checkable: true
- checked: document.italic
action: italicAction
}
ToolButton {
@@ -298,8 +366,6 @@ ApplicationWindow {
text: "\uF0CD" // icon-underline
font.family: "fontello"
focusPolicy: Qt.TabFocus
- checkable: true
- checked: document.underline
action: underlineAction
}
ToolButton {
@@ -307,21 +373,19 @@ ApplicationWindow {
text: "\uF0CC"
font.family: "fontello"
focusPolicy: Qt.TabFocus
- checkable: true
- checked: document.strikeout
- onClicked: document.strikeout = !document.strikeout
+ action: strikeoutAction
}
ToolButton {
id: fontFamilyToolButton
text: qsTr("\uE808") // icon-font
font.family: "fontello"
- font.bold: document.bold
- font.italic: document.italic
- font.underline: document.underline
- font.strikeout: document.strikeout
+ font.bold: textArea.cursorSelection.font.bold
+ font.italic: textArea.cursorSelection.font.italic
+ font.underline: textArea.cursorSelection.font.underline
+ font.strikeout: textArea.cursorSelection.font.strikeout
focusPolicy: Qt.TabFocus
onClicked: function () {
- fontDialog.selectedFont = document.font
+ fontDialog.selectedFont = textArea.cursorSelection.font
fontDialog.open()
}
}
@@ -331,14 +395,14 @@ ApplicationWindow {
font.family: "fontello"
focusPolicy: Qt.TabFocus
onClicked: function () {
- colorDialog.selectedColor = document.textColor
+ colorDialog.selectedColor = textArea.cursorSelection.color
colorDialog.open()
}
Rectangle {
width: aFontMetrics.width + 3
height: 2
- color: document.textColor
+ color: textArea.cursorSelection.color
parent: textColorButton.contentItem
anchors.horizontalCenter: parent.horizontalCenter
anchors.baseline: parent.baseline
@@ -363,36 +427,28 @@ ApplicationWindow {
text: "\uE803" // icon-align-left
font.family: "fontello"
focusPolicy: Qt.TabFocus
- checkable: true
- checked: document.alignment == Qt.AlignLeft
- onClicked: document.alignment = Qt.AlignLeft
+ action: alignLeftAction
}
ToolButton {
id: alignCenterButton
text: "\uE804" // icon-align-center
font.family: "fontello"
focusPolicy: Qt.TabFocus
- checkable: true
- checked: document.alignment == Qt.AlignHCenter
- onClicked: document.alignment = Qt.AlignHCenter
+ action: alignCenterAction
}
ToolButton {
id: alignRightButton
text: "\uE805" // icon-align-right
font.family: "fontello"
focusPolicy: Qt.TabFocus
- checkable: true
- checked: document.alignment == Qt.AlignRight
- onClicked: document.alignment = Qt.AlignRight
+ action: alignRightAction
}
ToolButton {
id: alignJustifyButton
text: "\uE806" // icon-align-justify
font.family: "fontello"
focusPolicy: Qt.TabFocus
- checkable: true
- checked: document.alignment == Qt.AlignJustify
- onClicked: document.alignment = Qt.AlignJustify
+ action: alignJustifyAction
}
}
}
@@ -405,13 +461,6 @@ ApplicationWindow {
selectionStart: textArea.selectionStart
selectionEnd: textArea.selectionEnd
- property alias family: document.font.family
- property alias bold: document.font.bold
- property alias italic: document.font.italic
- property alias underline: document.font.underline
- property alias strikeout: document.font.strikeout
- property alias size: document.font.pointSize
-
Component.onCompleted: {
if (Qt.application.arguments.length === 2)
textArea.textDocument.source = "file:" + Qt.application.arguments[1];
@@ -492,7 +541,7 @@ ApplicationWindow {
Platform.MenuItem {
text: qsTr("Font...")
onTriggered: function () {
- fontDialog.selectedFont = document.font
+ fontDialog.selectedFont = textArea.cursorSelection.font
fontDialog.open()
}
}
@@ -500,7 +549,7 @@ ApplicationWindow {
Platform.MenuItem {
text: qsTr("Color...")
onTriggered: function () {
- colorDialog.selectedColor = document.textColor
+ colorDialog.selectedColor = textArea.cursorSelection.color
colorDialog.open()
}
}
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt
index ebdd698de3..2ba1d560a6 100644
--- a/src/quick/CMakeLists.txt
+++ b/src/quick/CMakeLists.txt
@@ -212,6 +212,7 @@ qt_internal_add_qml_module(Quick
util/qquicksvgparser.cpp util/qquicksvgparser_p.h
util/qquicksystempalette.cpp util/qquicksystempalette_p.h
util/qquicktextmetrics.cpp util/qquicktextmetrics_p.h
+ util/qquicktextselection.cpp util/qquicktextselection_p.h
util/qquicktimeline.cpp
util/qquicktimeline_p_p.h
util/qquicktransition.cpp util/qquicktransition_p.h
diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp
index ede599cfa3..f9f62fcbf1 100644
--- a/src/quick/items/qquicktextedit.cpp
+++ b/src/quick/items/qquicktextedit.cpp
@@ -1165,6 +1165,24 @@ int QQuickTextEdit::positionAt(qreal x, qreal y) const
}
/*!
+ \qmlproperty QtQuick::TextSelection QtQuick::TextEdit::cursorSelection
+ \since 6.7
+ \preliminary
+
+ This property is an object that provides properties of the text that is
+ currently selected, if any, alongside the text cursor.
+
+ \sa selectedText, selectionStart, selectionEnd
+*/
+QQuickTextSelection *QQuickTextEdit::cursorSelection() const
+{
+ Q_D(const QQuickTextEdit);
+ if (!d->cursorSelection)
+ d->cursorSelection = new QQuickTextSelection(const_cast<QQuickTextEdit *>(this));
+ return d->cursorSelection;
+}
+
+/*!
\qmlmethod QtQuick::TextEdit::moveCursorSelection(int position, SelectionMode mode)
Moves the cursor to \a position and updates the selection according to the optional \a mode
@@ -3566,4 +3584,3 @@ QQuickPre64TextEdit::QQuickPre64TextEdit(QQuickItem *parent)
QT_END_NAMESPACE
#include "moc_qquicktextedit_p.cpp"
-
diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h
index fde1a8d591..f771321856 100644
--- a/src/quick/items/qquicktextedit_p.h
+++ b/src/quick/items/qquicktextedit_p.h
@@ -25,6 +25,7 @@ QT_BEGIN_NAMESPACE
class QTextDocument;
class QQuickTextDocument;
class QQuickTextEditPrivate;
+class QQuickTextSelection;
class QTextBlock;
class Q_QUICK_PRIVATE_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem, public QQuickTextInterface
@@ -79,6 +80,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem, pub
Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding RESET resetBottomPadding NOTIFY bottomPaddingChanged REVISION(2, 6) FINAL)
Q_PROPERTY(QString preeditText READ preeditText NOTIFY preeditTextChanged REVISION(2, 7) FINAL)
Q_PROPERTY(qreal tabStopDistance READ tabStopDistance WRITE setTabStopDistance NOTIFY tabStopDistanceChanged REVISION(2, 10) FINAL)
+ Q_PROPERTY(QQuickTextSelection* cursorSelection READ cursorSelection REVISION(6, 7) CONSTANT FINAL)
QML_NAMED_ELEMENT(TextEdit)
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
QML_ADDED_IN_VERSION(6, 4)
@@ -236,6 +238,8 @@ public:
Q_INVOKABLE void moveCursorSelection(int pos);
Q_INVOKABLE void moveCursorSelection(int pos, SelectionMode mode);
+ QQuickTextSelection *cursorSelection() const;
+
QRectF boundingRect() const override;
QRectF clipRect() const override;
diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h
index 8e89ebc54d..c4aef2ec3d 100644
--- a/src/quick/items/qquicktextedit_p_p.h
+++ b/src/quick/items/qquicktextedit_p_p.h
@@ -19,6 +19,8 @@
#include "qquickimplicitsizeitem_p_p.h"
#include "qquicktextutil_p.h"
+#include <QtQuick/private/qquicktextselection_p.h>
+
#include <QtQml/qqml.h>
#include <QtCore/qlist.h>
#include <private/qlazilyallocated_p.h>
@@ -167,6 +169,7 @@ public:
QTextDocument *document = nullptr;
QQuickTextControl *control = nullptr;
QQuickTextDocument *quickDocument = nullptr;
+ mutable QQuickTextSelection *cursorSelection = nullptr;
QList<Node> textNodeMap;
QList<QQuickPixmap *> pixmapsInProgress;
diff --git a/src/quick/util/qquicktextselection.cpp b/src/quick/util/qquicktextselection.cpp
new file mode 100644
index 0000000000..2f1a7dbc6d
--- /dev/null
+++ b/src/quick/util/qquicktextselection.cpp
@@ -0,0 +1,188 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquicktextselection_p.h"
+
+#include <QFont>
+#include <QTextOption>
+#include <QtQuick/private/qquicktextcontrol_p.h>
+#include <QtQuick/private/qquicktextcontrol_p_p.h>
+#include <QtQuick/private/qquicktextedit_p_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype TextSelection
+ \instantiates QQuickTextSelection
+ \inqmlmodule QtQuick
+ \ingroup qtquick-visual
+ \ingroup qtquick-input
+ \brief Represents a contiguous selection of text and its properties.
+ \since 6.7
+
+ \l {QtQuick::TextEdit::cursorSelection}{TextEdit.cursorSelection}
+ represents the range of text that is currently selected (for example by
+ dragging the mouse). It can be used to query and modify the selected text,
+ as well as properties in the \l {QTextCharFormat}{character} and
+ \l {QTextBlockFormat}{block} formats.
+
+ \note This API is considered tech preview and may change or be removed in
+ future versions of Qt.
+
+ \sa TextEdit, QTextCursor
+*/
+
+/*! \internal
+ QQuickTextSelection provides QML API using QTextCursor.
+ QQuickTextControl owns a text cursor, and one instance of
+ QQuickTextSelection represents it and delegates all operations to it.
+*/
+QQuickTextSelection::QQuickTextSelection(QObject *parent)
+ : QObject(parent)
+{
+ // When QQuickTextEdit creates its cursorSelection, it passes itself as the parent
+ if (auto *textEdit = qmlobject_cast<QQuickTextEdit *>(parent)) {
+ m_doc = textEdit->textDocument();
+ m_control = QQuickTextEditPrivate::get(textEdit)->control;
+ connect(m_control, &QQuickTextControl::currentCharFormatChanged,
+ this, &QQuickTextSelection::updateFromCharFormat);
+ connect(m_control, &QQuickTextControl::cursorPositionChanged,
+ this, &QQuickTextSelection::updateFromBlockFormat);
+ }
+}
+
+/*!
+ \qmlproperty string QtQuick::TextSelection::text
+
+ The selected text, without any rich text markup.
+
+ Setting this property replaces the selected text with the given string.
+*/
+QString QQuickTextSelection::text() const
+{
+ return cursor().selectedText();
+}
+
+void QQuickTextSelection::setText(const QString &text)
+{
+ auto cur = cursor();
+ if (cur.selectedText() == text)
+ return;
+
+ cur.insertText(text);
+ emit textChanged();
+}
+
+/*!
+ \qmlproperty color QtQuick::TextSelection::font
+
+ The font of the selected text.
+
+ \sa QTextCharFormat::font()
+*/
+QFont QQuickTextSelection::font() const
+{
+ return cursor().charFormat().font();
+}
+
+void QQuickTextSelection::setFont(const QFont &font)
+{
+ auto cur = cursor();
+ if (cur.selection().isEmpty())
+ cur.select(QTextCursor::WordUnderCursor);
+
+ if (font == cur.charFormat().font())
+ return;
+
+ QTextCharFormat fmt;
+ fmt.setFont(font);
+ cur.mergeCharFormat(fmt);
+ emit fontChanged();
+}
+
+/*!
+ \qmlproperty color QtQuick::TextSelection::color
+
+ The foreground color of the selected text.
+
+ \sa QTextCharFormat::foreground()
+*/
+QColor QQuickTextSelection::color() const
+{
+ return cursor().charFormat().foreground().color();
+}
+
+void QQuickTextSelection::setColor(QColor color)
+{
+ auto cur = cursor();
+ if (cur.selection().isEmpty())
+ cur.select(QTextCursor::WordUnderCursor);
+
+ if (color == cur.charFormat().foreground().color())
+ return;
+
+ QTextCharFormat fmt;
+ fmt.setForeground(color);
+ cur.mergeCharFormat(fmt);
+ emit colorChanged();
+}
+
+/*!
+ \qmlproperty enumeration QtQuick::TextSelection::alignment
+
+ The alignment of the block containing the selected text.
+
+ \sa QTextBlockFormat::alignment()
+*/
+Qt::Alignment QQuickTextSelection::alignment() const
+{
+ return cursor().blockFormat().alignment();
+}
+
+void QQuickTextSelection::setAlignment(Qt::Alignment align)
+{
+ if (align == alignment())
+ return;
+
+ QTextBlockFormat format;
+ format.setAlignment(align);
+ cursor().mergeBlockFormat(format);
+ emit alignmentChanged();
+}
+
+/*! \internal
+ Return the cursor, which is either the graphically-manipulable cursor from
+ QQuickTextControl if that is set, or else the internally-stored cursor
+ with which the user is trying to mutate and/or monitor the underlying document,
+ in the case that TextSelection is declared in QML.
+*/
+QTextCursor QQuickTextSelection::cursor() const
+{
+ if (m_control)
+ return m_control->textCursor();
+ return m_cursor;
+}
+
+inline void QQuickTextSelection::updateFromCharFormat(const QTextCharFormat &fmt)
+{
+ if (fmt.font() != m_charFormat.font())
+ emit fontChanged();
+ if (fmt.foreground().color() != m_charFormat.foreground().color())
+ emit colorChanged();
+
+ m_charFormat = fmt;
+}
+
+inline void QQuickTextSelection::updateFromBlockFormat()
+{
+ QTextBlockFormat fmt = cursor().blockFormat();
+
+ if (fmt.alignment() != m_blockFormat.alignment())
+ emit alignmentChanged();
+
+ m_blockFormat = fmt;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qquicktextselection_p.cpp"
diff --git a/src/quick/util/qquicktextselection_p.h b/src/quick/util/qquicktextselection_p.h
new file mode 100644
index 0000000000..f645875b2d
--- /dev/null
+++ b/src/quick/util/qquicktextselection_p.h
@@ -0,0 +1,81 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQUICKTEXTSELECTION_H
+#define QQUICKTEXTSELECTION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtquickglobal_p.h>
+
+#include <QtQuick/qquicktextdocument.h>
+
+#include <QtQml/qqml.h>
+
+#include <QtGui/qtextcursor.h>
+
+QT_BEGIN_NAMESPACE
+
+class QFont;
+class QQuickTextControl;
+
+class Q_QUICK_PRIVATE_EXPORT QQuickTextSelection : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL)
+ Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged FINAL)
+ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
+ Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged FINAL)
+
+ QML_ANONYMOUS
+ QML_ADDED_IN_VERSION(6, 7)
+
+public:
+ explicit QQuickTextSelection(QObject *parent = nullptr);
+
+ QString text() const;
+ void setText(const QString &text);
+
+ QFont font() const;
+ void setFont(const QFont &font);
+
+ QColor color() const;
+ void setColor(QColor color);
+
+ Qt::Alignment alignment() const;
+ void setAlignment(Qt::Alignment align);
+
+Q_SIGNALS:
+ void textChanged();
+ void fontChanged();
+ void colorChanged();
+ void alignmentChanged();
+
+private:
+ QTextCursor cursor() const;
+ void updateFromCharFormat(const QTextCharFormat &fmt);
+ void updateFromBlockFormat();
+
+private:
+ QTextCursor m_cursor;
+ QTextCharFormat m_charFormat;
+ QTextBlockFormat m_blockFormat;
+ QQuickTextDocument *m_doc = nullptr;
+ QQuickTextControl *m_control = nullptr;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickTextSelection)
+
+#endif // QQUICKTEXTSELECTION_H
diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
index 5ad49ffd1b..458b9301c0 100644
--- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
+++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
@@ -226,6 +226,8 @@ private slots:
void rtlAlignmentInColumnLayout_QTBUG_112858();
+ void fontManipulationWithCursorSelection();
+
private:
void simulateKeys(QWindow *window, const QList<Key> &keys);
#if QT_CONFIG(shortcut)
@@ -6596,6 +6598,73 @@ void tst_qquicktextedit::rtlAlignmentInColumnLayout_QTBUG_112858()
}
}
+void tst_qquicktextedit::fontManipulationWithCursorSelection()
+{
+ QString testStr = standard[0];//TODO: What should happen for multiline/rich text?
+ QString componentStr = "import QtQuick 2.0\nTextEdit { text: \""+ testStr +"\"; }";
+ QQmlComponent texteditComponent(&engine);
+ texteditComponent.setData(componentStr.toLatin1(), QUrl());
+ QQuickTextEdit *textEditObject = qobject_cast<QQuickTextEdit *>(texteditComponent.create());
+ QVERIFY(textEditObject != nullptr);
+
+ const int originalStartPos = 0;
+ const int originalEndPos = (testStr.size() - 1) / 2;
+
+ textEditObject->select(originalStartPos, originalEndPos);
+ QCOMPARE(textEditObject->selectionStart(), originalStartPos);
+ QCOMPARE(textEditObject->selectionEnd(), originalEndPos);
+
+ QCOMPARE(textEditObject->cursorSelection()->text(), textEditObject->text().mid(originalStartPos, originalEndPos));
+
+ // test font manipulation
+ QFont font = textEditObject->cursorSelection()->font();
+ QVERIFY(!font.bold());
+ font.setBold(true);
+ textEditObject->cursorSelection()->setFont(font);
+ QVERIFY(textEditObject->cursorSelection()->font().bold());
+
+ // test color manipulation
+ QCOMPARE_NE(textEditObject->cursorSelection()->color(), QColorConstants::Cyan);
+ textEditObject->cursorSelection()->setColor(QColorConstants::Cyan);
+ QCOMPARE(textEditObject->cursorSelection()->color(), QColorConstants::Cyan);
+
+ // test alignment
+ QCOMPARE(textEditObject->cursorSelection()->alignment(), Qt::AlignLeft);
+ textEditObject->cursorSelection()->setAlignment(Qt::AlignRight);
+ QCOMPARE(textEditObject->cursorSelection()->alignment(), Qt::AlignRight);
+
+ // change seleciton and verify that we don't keep the same formatting
+ const int newStartPos = testStr.size() / 2;
+ const int newEndPos = testStr.size() - 1;
+
+ textEditObject->select(newStartPos, newEndPos);
+ QCOMPARE(textEditObject->selectionStart(), newStartPos);
+ QCOMPARE(textEditObject->selectionEnd(), newEndPos);
+ QVERIFY(!textEditObject->cursorSelection()->font().bold());
+ QCOMPARE_NE(textEditObject->cursorSelection()->color(), QColorConstants::Cyan);
+ QEXPECT_FAIL("", "The text alignment doesn't update when changing selection", Continue);
+ QCOMPARE(textEditObject->cursorSelection()->alignment(), Qt::AlignLeft);
+
+ // change back to the previous fragment, and verify that we have the old formatting
+ textEditObject->select(originalStartPos, originalEndPos);
+ QVERIFY(font.bold());
+ QCOMPARE(textEditObject->cursorSelection()->color(), QColorConstants::Cyan);
+ QCOMPARE(textEditObject->cursorSelection()->alignment(), Qt::AlignRight);
+
+ // test text manipulation
+ textEditObject->cursorSelection()->setText("Q");
+ QEXPECT_FAIL("", "QQuickTextSelection::text doesn't currently work correctly", Continue);
+ QCOMPARE(textEditObject->text(), QLatin1String("Q%1").arg(testStr.mid(newStartPos, newEndPos)));
+
+ // Make sure that QQuickTextEdit::setFont() affects all blocks
+ font.setItalic(true);
+ font.setWeight(QFont::Black);
+ textEditObject->setFont(font);
+ const auto *doc = textEditObject->textDocument()->textDocument();
+ for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next())
+ QCOMPARE(block.charFormat().font(), font);
+}
+
QT_END_NAMESPACE
QTEST_MAIN(tst_qquicktextedit)