Rework MessageDelegate in cpp
Rework MessageDelegate to be mostly a cpp class. This allows us to only load the components that are actually needed saving memory. In testing using memtest it saved ~30% versus the current implementation.
This commit is contained in:
@@ -15,7 +15,7 @@ target_link_libraries(timeline-memtest PUBLIC
|
|||||||
Qt::Gui
|
Qt::Gui
|
||||||
Qt::QuickControls2
|
Qt::QuickControls2
|
||||||
Qt::Widgets
|
Qt::Widgets
|
||||||
KF6::I18n
|
KF6::I18nQml
|
||||||
KF6::Kirigami
|
KF6::Kirigami
|
||||||
QuotientQt6
|
QuotientQt6
|
||||||
LibNeoChat
|
LibNeoChat
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ QQC2.ApplicationWindow {
|
|||||||
height: root.height
|
height: root.height
|
||||||
|
|
||||||
contentItem: ListView {
|
contentItem: ListView {
|
||||||
|
cacheBuffer: 1000000
|
||||||
model: messageFilterModel
|
model: messageFilterModel
|
||||||
|
|
||||||
delegate: EventDelegate {
|
delegate: EventDelegate {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
|
|
||||||
|
#include <KLocalizedQmlContext>
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
#include "memtesttimelinemodel.h"
|
#include "memtesttimelinemodel.h"
|
||||||
@@ -19,12 +20,15 @@ int main(int argc, char **argv)
|
|||||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
|
|
||||||
|
KLocalization::setupLocalizedContext(&engine);
|
||||||
|
|
||||||
MemTestTimelineModel *memTestTimelineModel = new MemTestTimelineModel;
|
MemTestTimelineModel *memTestTimelineModel = new MemTestTimelineModel;
|
||||||
MessageFilterModel *messageFilterModel = new MessageFilterModel(nullptr, memTestTimelineModel);
|
MessageFilterModel *messageFilterModel = new MessageFilterModel(nullptr, memTestTimelineModel);
|
||||||
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
|
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
|
||||||
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
|
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
|
||||||
|
|
||||||
|
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,7 +241,7 @@
|
|||||||
"formatted_body": "This is an example<br>text message",
|
"formatted_body": "This is an example<br>text message",
|
||||||
"msgtype": "m.text"
|
"msgtype": "m.text"
|
||||||
},
|
},
|
||||||
"event_id": "$1000000000000:example.org",
|
"event_id": 0,
|
||||||
"origin_server_ts": 1000000000000,
|
"origin_server_ts": 1000000000000,
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
"sender": "@example:example.org",
|
"sender": "@example:example.org",
|
||||||
@@ -255,7 +255,7 @@
|
|||||||
"body": "This is a highlight @bob:example.org",
|
"body": "This is a highlight @bob:example.org",
|
||||||
"msgtype": "m.text"
|
"msgtype": "m.text"
|
||||||
},
|
},
|
||||||
"event_id": "$1000000000001:example.org",
|
"event_id": 1,
|
||||||
"origin_server_ts": 1000000000001,
|
"origin_server_ts": 1000000000001,
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
"sender": "@example:example.org",
|
"sender": "@example:example.org",
|
||||||
@@ -266,11 +266,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"m.relates_to": {
|
"m.relates_to": {
|
||||||
"event_id": "$1000000000001:example.org",
|
"event_id": 1,
|
||||||
"key": "👍",
|
"key": "👍",
|
||||||
"rel_type": "m.annotation"
|
"rel_type": "m.annotation"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"origin_server_ts": 1000000000002,
|
"origin_server_ts": 1000000000002,
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
@@ -279,7 +279,7 @@
|
|||||||
"unsigned": {
|
"unsigned": {
|
||||||
"age": 390159120
|
"age": 390159120
|
||||||
},
|
},
|
||||||
"event_id": "$1000000000002:example.org",
|
"event_id": 2,
|
||||||
"age": 390159120
|
"age": 390159120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
"formatted_body": "reply",
|
"formatted_body": "reply",
|
||||||
"m.relates_to": {
|
"m.relates_to": {
|
||||||
"m.in_reply_to": {
|
"m.in_reply_to": {
|
||||||
"event_id": "$1000000000000:example.org"
|
"event_id": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"msgtype": "m.text"
|
"msgtype": "m.text"
|
||||||
@@ -300,7 +300,7 @@
|
|||||||
"unsigned": {
|
"unsigned": {
|
||||||
"age": 98
|
"age": 98
|
||||||
},
|
},
|
||||||
"event_id": "$1000000000003:example.org",
|
"event_id": 3,
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -317,7 +317,7 @@
|
|||||||
"uri": "geo:51.7035,-1.14394"
|
"uri": "geo:51.7035,-1.14394"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"event_id": "$1000000000004:example.org",
|
"event_id": 4,
|
||||||
"origin_server_ts": 1000000000004,
|
"origin_server_ts": 1000000000004,
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
"sender": "@example:example.org",
|
"sender": "@example:example.org",
|
||||||
@@ -333,7 +333,7 @@
|
|||||||
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");\n\n return app.exec();\n}\n</code></pre>",
|
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");\n\n return app.exec();\n}\n</code></pre>",
|
||||||
"msgtype": "m.text"
|
"msgtype": "m.text"
|
||||||
},
|
},
|
||||||
"event_id": "$1000000000005:example.org",
|
"event_id": 5,
|
||||||
"origin_server_ts": 1000000000005,
|
"origin_server_ts": 1000000000005,
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
"sender": "@bob:example.org",
|
"sender": "@bob:example.org",
|
||||||
@@ -347,7 +347,7 @@
|
|||||||
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
|
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
|
||||||
"msgtype": "m.text"
|
"msgtype": "m.text"
|
||||||
},
|
},
|
||||||
"event_id": "$1000000000006:example.org",
|
"event_id": 6,
|
||||||
"origin_server_ts": 1000000000006,
|
"origin_server_ts": 1000000000006,
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
"sender": "@example:example.org",
|
"sender": "@example:example.org",
|
||||||
@@ -363,7 +363,7 @@
|
|||||||
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
|
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
|
||||||
"msgtype": "m.text"
|
"msgtype": "m.text"
|
||||||
},
|
},
|
||||||
"event_id": "$1000000000007:example.org",
|
"event_id": 7,
|
||||||
"origin_server_ts": 1000000000007,
|
"origin_server_ts": 1000000000007,
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
"sender": "@example:example.org",
|
"sender": "@example:example.org",
|
||||||
@@ -371,282 +371,6 @@
|
|||||||
"unsigned": {
|
"unsigned": {
|
||||||
"age": 1232
|
"age": 1232
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "This is an example text message",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "This is an example<br>text message",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000008:example.org",
|
|
||||||
"origin_server_ts": 1000000000008,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1232
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "This is a highlight @bob:example.org",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000009:example.org",
|
|
||||||
"origin_server_ts": 1000000000009,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1233
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"m.relates_to": {
|
|
||||||
"event_id": "$1000000000009:example.org",
|
|
||||||
"key": "👍",
|
|
||||||
"rel_type": "m.annotation"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"origin_server_ts": 1000000000010,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@alice:example.org",
|
|
||||||
"type": "m.reaction",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 390159120
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000010:example.org",
|
|
||||||
"age": 390159120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "reply",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "reply",
|
|
||||||
"m.relates_to": {
|
|
||||||
"m.in_reply_to": {
|
|
||||||
"event_id": "$1000000000008:example.org"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"origin_server_ts": 1000000000011,
|
|
||||||
"sender": "@alice:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 98
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000011:example.org",
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"age": 96845207,
|
|
||||||
"content": {
|
|
||||||
"body": "Lat: 51.7035, Lon: -1.14394",
|
|
||||||
"geo_uri": "geo:51.7035,-1.14394",
|
|
||||||
"msgtype": "m.location",
|
|
||||||
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
|
|
||||||
"org.matrix.msc3488.asset": {
|
|
||||||
"type": "m.pin"
|
|
||||||
},
|
|
||||||
"org.matrix.msc3488.location": {
|
|
||||||
"uri": "geo:51.7035,-1.14394"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000012:example.org",
|
|
||||||
"origin_server_ts": 1000000000012,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 96845207
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "```cpp\nint main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(\"neochat\"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(\"org.kde.neochat.timeline-memtest\", \"Main\");\n\n return app.exec();\n}\n```",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");\n\n return app.exec();\n}\n</code></pre>",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000013:example.org",
|
|
||||||
"origin_server_ts": 1000000000013,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@bob:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1233
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000014:example.org",
|
|
||||||
"origin_server_ts": 1000000000014,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1232
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000015:example.org",
|
|
||||||
"origin_server_ts": 1000000000015,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1232
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "This is an example text message",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "This is an example<br>text message",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000016:example.org",
|
|
||||||
"origin_server_ts": 1000000000016,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1232
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "This is a highlight @bob:example.org",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000017:example.org",
|
|
||||||
"origin_server_ts": 1000000000017,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1233
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"m.relates_to": {
|
|
||||||
"event_id": "$1000000000017:example.org",
|
|
||||||
"key": "👍",
|
|
||||||
"rel_type": "m.annotation"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"origin_server_ts": 1000000000018,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@alice:example.org",
|
|
||||||
"type": "m.reaction",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 390159120
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000018:example.org",
|
|
||||||
"age": 390159120
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "reply",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "reply",
|
|
||||||
"m.relates_to": {
|
|
||||||
"m.in_reply_to": {
|
|
||||||
"event_id": "$1000000000016:example.org"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"origin_server_ts": 1000000000019,
|
|
||||||
"sender": "@alice:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 98
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000019:example.org",
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"age": 96845207,
|
|
||||||
"content": {
|
|
||||||
"body": "Lat: 51.7035, Lon: -1.14394",
|
|
||||||
"geo_uri": "geo:51.7035,-1.14394",
|
|
||||||
"msgtype": "m.location",
|
|
||||||
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
|
|
||||||
"org.matrix.msc3488.asset": {
|
|
||||||
"type": "m.pin"
|
|
||||||
},
|
|
||||||
"org.matrix.msc3488.location": {
|
|
||||||
"uri": "geo:51.7035,-1.14394"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000020:example.org",
|
|
||||||
"origin_server_ts": 1000000000020,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 96845207
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "```cpp\nint main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(\"neochat\"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(\"org.kde.neochat.timeline-memtest\", \"Main\");\n\n return app.exec();\n}\n```",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");\n\n return app.exec();\n}\n</code></pre>",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000021:example.org",
|
|
||||||
"origin_server_ts": 1000000000021,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@bob:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1233
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000022:example.org",
|
|
||||||
"origin_server_ts": 1000000000022,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1232
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
|
|
||||||
"msgtype": "m.text"
|
|
||||||
},
|
|
||||||
"event_id": "$1000000000023:example.org",
|
|
||||||
"origin_server_ts": 1000000000023,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"type": "m.room.message",
|
|
||||||
"unsigned": {
|
|
||||||
"age": 1232
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"limited": true,
|
"limited": true,
|
||||||
|
|||||||
@@ -39,10 +39,52 @@ public:
|
|||||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||||
testSyncFile.open(QIODevice::ReadOnly);
|
testSyncFile.open(QIODevice::ReadOnly);
|
||||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
|
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
|
||||||
|
auto timelineJson = testSyncJson["timeline"_L1].toObject();
|
||||||
|
timelineJson["events"_L1] = multiplyEvents(timelineJson["events"_L1].toArray(), 100);
|
||||||
|
testSyncJson["timeline"_L1] = timelineJson;
|
||||||
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson);
|
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson);
|
||||||
update(std::move(roomData));
|
update(std::move(roomData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonArray multiplyEvents(QJsonArray events, int factor)
|
||||||
|
{
|
||||||
|
QJsonArray newArray;
|
||||||
|
int eventNum = 0;
|
||||||
|
int ts = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < factor; ++i) {
|
||||||
|
for (const auto &event : events) {
|
||||||
|
auto eventObject = event.toObject();
|
||||||
|
auto contentJson = eventObject["content"_L1].toObject();
|
||||||
|
if (contentJson.contains("m.relates_to"_L1)) {
|
||||||
|
auto relatesToJson = contentJson["m.relates_to"_L1].toObject();
|
||||||
|
if (relatesToJson.contains("m.in_reply_to"_L1)) {
|
||||||
|
auto replyJson = relatesToJson["m.in_reply_to"_L1].toObject();
|
||||||
|
const auto currentId = eventObject["event_id"_L1].toInt();
|
||||||
|
const auto currentReplyId = replyJson["event_id"_L1].toInt();
|
||||||
|
replyJson["event_id"_L1] = "$%1:example.org"_L1.arg(QString::number(eventNum - (currentId - currentReplyId)));
|
||||||
|
relatesToJson["m.in_reply_to"_L1] = replyJson;
|
||||||
|
} else if (relatesToJson.contains("event_id"_L1)) {
|
||||||
|
const auto currentId = eventObject["event_id"_L1].toInt();
|
||||||
|
const auto currentRelationId = relatesToJson["event_id"_L1].toInt();
|
||||||
|
relatesToJson["event_id"_L1] = "$%1:example.org"_L1.arg(QString::number(eventNum - (currentId - currentRelationId)));
|
||||||
|
}
|
||||||
|
contentJson["m.relates_to"_L1] = relatesToJson;
|
||||||
|
eventObject["content"_L1] = contentJson;
|
||||||
|
}
|
||||||
|
eventObject["event_id"_L1] = "$%1:example.org"_L1.arg(QString::number(eventNum));
|
||||||
|
eventObject["origin_server_ts"_L1] = ts;
|
||||||
|
auto unsignedJson = eventObject["unsigned"_L1].toObject();
|
||||||
|
unsignedJson["age"_L1] = ts;
|
||||||
|
eventObject["unsigned"_L1] = unsignedJson;
|
||||||
|
newArray.append(eventObject);
|
||||||
|
++eventNum;
|
||||||
|
++ts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,14 +26,12 @@ void DelegateSizeHelper::setParentItem(QQuickItem *parentItem)
|
|||||||
|
|
||||||
if (m_parentItem) {
|
if (m_parentItem) {
|
||||||
connect(m_parentItem, &QQuickItem::widthChanged, this, [this]() {
|
connect(m_parentItem, &QQuickItem::widthChanged, this, [this]() {
|
||||||
Q_EMIT availablePercentageWidthChanged();
|
calcWidthsChanged();
|
||||||
Q_EMIT availableWidthChanged();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_EMIT parentItemChanged();
|
Q_EMIT parentItemChanged();
|
||||||
Q_EMIT availablePercentageWidthChanged();
|
calcWidthsChanged();
|
||||||
Q_EMIT availableWidthChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal DelegateSizeHelper::leftPadding() const
|
qreal DelegateSizeHelper::leftPadding() const
|
||||||
@@ -48,8 +46,7 @@ void DelegateSizeHelper::setLeftPadding(qreal leftPadding)
|
|||||||
}
|
}
|
||||||
m_leftPadding = leftPadding;
|
m_leftPadding = leftPadding;
|
||||||
Q_EMIT leftPaddingChanged();
|
Q_EMIT leftPaddingChanged();
|
||||||
Q_EMIT availablePercentageWidthChanged();
|
calcWidthsChanged();
|
||||||
Q_EMIT availableWidthChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal DelegateSizeHelper::rightPadding() const
|
qreal DelegateSizeHelper::rightPadding() const
|
||||||
@@ -64,8 +61,7 @@ void DelegateSizeHelper::setRightPadding(qreal rightPadding)
|
|||||||
}
|
}
|
||||||
m_rightPadding = rightPadding;
|
m_rightPadding = rightPadding;
|
||||||
Q_EMIT rightPaddingChanged();
|
Q_EMIT rightPaddingChanged();
|
||||||
Q_EMIT availablePercentageWidthChanged();
|
calcWidthsChanged();
|
||||||
Q_EMIT availableWidthChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal DelegateSizeHelper::startBreakpoint() const
|
qreal DelegateSizeHelper::startBreakpoint() const
|
||||||
@@ -80,6 +76,7 @@ void DelegateSizeHelper::setStartBreakpoint(qreal startBreakpoint)
|
|||||||
}
|
}
|
||||||
m_startBreakpoint = startBreakpoint;
|
m_startBreakpoint = startBreakpoint;
|
||||||
Q_EMIT startBreakpointChanged();
|
Q_EMIT startBreakpointChanged();
|
||||||
|
calcWidthsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal DelegateSizeHelper::endBreakpoint() const
|
qreal DelegateSizeHelper::endBreakpoint() const
|
||||||
@@ -94,6 +91,7 @@ void DelegateSizeHelper::setEndBreakpoint(qreal endBreakpoint)
|
|||||||
}
|
}
|
||||||
m_endBreakpoint = endBreakpoint;
|
m_endBreakpoint = endBreakpoint;
|
||||||
Q_EMIT endBreakpointChanged();
|
Q_EMIT endBreakpointChanged();
|
||||||
|
calcWidthsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
int DelegateSizeHelper::startPercentWidth() const
|
int DelegateSizeHelper::startPercentWidth() const
|
||||||
@@ -108,6 +106,7 @@ void DelegateSizeHelper::setStartPercentWidth(int startPercentWidth)
|
|||||||
}
|
}
|
||||||
m_startPercentWidth = startPercentWidth;
|
m_startPercentWidth = startPercentWidth;
|
||||||
Q_EMIT startPercentWidthChanged();
|
Q_EMIT startPercentWidthChanged();
|
||||||
|
calcWidthsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
int DelegateSizeHelper::endPercentWidth() const
|
int DelegateSizeHelper::endPercentWidth() const
|
||||||
@@ -122,6 +121,7 @@ void DelegateSizeHelper::setEndPercentWidth(int endPercentWidth)
|
|||||||
}
|
}
|
||||||
m_endPercentWidth = endPercentWidth;
|
m_endPercentWidth = endPercentWidth;
|
||||||
Q_EMIT endPercentWidthChanged();
|
Q_EMIT endPercentWidthChanged();
|
||||||
|
calcWidthsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal DelegateSizeHelper::maxWidth() const
|
qreal DelegateSizeHelper::maxWidth() const
|
||||||
@@ -143,6 +143,7 @@ void DelegateSizeHelper::setMaxWidth(qreal maxWidth)
|
|||||||
}
|
}
|
||||||
m_maxWidth = maxWidth;
|
m_maxWidth = maxWidth;
|
||||||
Q_EMIT maxWidthChanged();
|
Q_EMIT maxWidthChanged();
|
||||||
|
calcWidthsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal DelegateSizeHelper::maxAvailableWidth() const
|
qreal DelegateSizeHelper::maxAvailableWidth() const
|
||||||
@@ -188,4 +189,20 @@ qreal DelegateSizeHelper::availableWidth() const
|
|||||||
return std::round(std::min(absoluteWidth, maxWidth()));
|
return std::round(std::min(absoluteWidth, maxWidth()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qreal DelegateSizeHelper::leftX() const
|
||||||
|
{
|
||||||
|
return m_leftPadding + (maxAvailableWidth() - availableWidth()) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal DelegateSizeHelper::rightX() const
|
||||||
|
{
|
||||||
|
return m_leftPadding + maxAvailableWidth() - (maxAvailableWidth() - availableWidth()) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DelegateSizeHelper::calcWidthsChanged()
|
||||||
|
{
|
||||||
|
Q_EMIT availablePercentageWidthChanged();
|
||||||
|
Q_EMIT availableWidthChanged();
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_delegatesizehelper.cpp"
|
#include "moc_delegatesizehelper.cpp"
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class DelegateSizeHelper : public QObject
|
|||||||
*
|
*
|
||||||
* @sa parentWidth, startBreakpoint, endBreakpoint
|
* @sa parentWidth, startBreakpoint, endBreakpoint
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(int availablePercentageWidth READ availablePercentageWidth NOTIFY availablePercentageWidthChanged)
|
Q_PROPERTY(int availablePercentageWidth READ availablePercentageWidth NOTIFY availableWidthChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The width (in px) of the component at the current parentWidth.
|
* @brief The width (in px) of the component at the current parentWidth.
|
||||||
@@ -124,6 +124,16 @@ public:
|
|||||||
int availablePercentageWidth() const;
|
int availablePercentageWidth() const;
|
||||||
qreal availableWidth() const;
|
qreal availableWidth() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The left x position of the content column.
|
||||||
|
*/
|
||||||
|
qreal leftX() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The right x position of the content column.
|
||||||
|
*/
|
||||||
|
qreal rightX() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void parentItemChanged();
|
void parentItemChanged();
|
||||||
void leftPaddingChanged();
|
void leftPaddingChanged();
|
||||||
@@ -148,5 +158,7 @@ private:
|
|||||||
int m_endPercentWidth = 85;
|
int m_endPercentWidth = 85;
|
||||||
std::optional<qreal> m_maxWidth = std::nullopt;
|
std::optional<qreal> m_maxWidth = std::nullopt;
|
||||||
|
|
||||||
|
void calcWidthsChanged();
|
||||||
|
|
||||||
int calculateAvailablePercentageWidth() const;
|
int calculateAvailablePercentageWidth() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||||
|
|
||||||
Flow {
|
RowLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var avatarSize: Kirigami.Units.iconSizes.small
|
property var avatarSize: Kirigami.Units.iconSizes.small
|
||||||
@@ -33,6 +34,11 @@ Flow {
|
|||||||
}
|
}
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: excessAvatarsLabel
|
id: excessAvatarsLabel
|
||||||
|
|
||||||
|
Layout.preferredWidth: Math.max(excessAvatarsTextMetrics.advanceWidth + Kirigami.Units.largeSpacing * 2, height)
|
||||||
|
Layout.preferredHeight: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
visible: text !== ""
|
visible: text !== ""
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
@@ -41,18 +47,17 @@ Flow {
|
|||||||
|
|
||||||
background: Kirigami.ShadowedRectangle {
|
background: Kirigami.ShadowedRectangle {
|
||||||
color: Kirigami.Theme.backgroundColor
|
color: Kirigami.Theme.backgroundColor
|
||||||
Kirigami.Theme.inherit: false
|
radius: Math.ceil(height / 2)
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
shadow {
|
||||||
radius: height / 2
|
size: Kirigami.Units.smallSpacing
|
||||||
shadow.size: Kirigami.Units.smallSpacing
|
color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||||
shadow.color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
}
|
||||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
border {
|
||||||
border.width: 1
|
color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
height: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing
|
|
||||||
width: Math.max(excessAvatarsTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height)
|
|
||||||
|
|
||||||
TextMetrics {
|
TextMetrics {
|
||||||
id: excessAvatarsTextMetrics
|
id: excessAvatarsTextMetrics
|
||||||
text: excessAvatarsLabel.text
|
text: excessAvatarsLabel.text
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ ecm_add_qml_module(Timeline GENERATE_PLUGIN_SOURCE
|
|||||||
locationhelper.cpp
|
locationhelper.cpp
|
||||||
mediasizehelper.cpp
|
mediasizehelper.cpp
|
||||||
messageattached.cpp
|
messageattached.cpp
|
||||||
|
messagedelegate.cpp
|
||||||
pollhandler.cpp
|
pollhandler.cpp
|
||||||
timelinedelegate.cpp
|
timelinedelegate.cpp
|
||||||
enums/delegatetype.h
|
enums/delegatetype.h
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import org.kde.neochat.libneochat as LibNeoChat
|
|||||||
* This component also supports a compact mode where the padding is adjusted, the
|
* This component also supports a compact mode where the padding is adjusted, the
|
||||||
* background is hidden and the delegate spans the full width of the timeline.
|
* background is hidden and the delegate spans the full width of the timeline.
|
||||||
*/
|
*/
|
||||||
TimelineDelegate {
|
MessageDelegateBase {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,20 +44,6 @@ TimelineDelegate {
|
|||||||
*/
|
*/
|
||||||
required property string eventId
|
required property string eventId
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The message author.
|
|
||||||
*
|
|
||||||
* A Quotient::RoomMember object.
|
|
||||||
*
|
|
||||||
* @sa Quotient::RoomMember
|
|
||||||
*/
|
|
||||||
required property NeochatRoomMember author
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Whether the message author should be shown.
|
|
||||||
*/
|
|
||||||
required property bool showAuthor
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The model to visualise the content of the message.
|
* @brief The model to visualise the content of the message.
|
||||||
*/
|
*/
|
||||||
@@ -68,26 +54,11 @@ TimelineDelegate {
|
|||||||
*/
|
*/
|
||||||
required property string section
|
required property string section
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Whether the section header should be shown.
|
|
||||||
*/
|
|
||||||
required property bool showSection
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A model with the first 5 other user read markers for this message.
|
* @brief A model with the first 5 other user read markers for this message.
|
||||||
*/
|
*/
|
||||||
required property var readMarkers
|
required property var readMarkers
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Whether the other user read marker component should be shown.
|
|
||||||
*/
|
|
||||||
required property bool showReadMarkers
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Whether the message in a thread.
|
|
||||||
*/
|
|
||||||
required property bool isThreaded
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The Matrix ID of the root message in the thread, if any.
|
* @brief The Matrix ID of the root message in the thread, if any.
|
||||||
*/
|
*/
|
||||||
@@ -130,7 +101,7 @@ TimelineDelegate {
|
|||||||
*
|
*
|
||||||
* @note Used for positioning the hover actions.
|
* @note Used for positioning the hover actions.
|
||||||
*/
|
*/
|
||||||
readonly property alias bubbleY: mainContainer.y
|
readonly property alias bubbleY: bubble.y
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The width of the message bubble.
|
* @brief The width of the message bubble.
|
||||||
@@ -164,179 +135,83 @@ TimelineDelegate {
|
|||||||
*/
|
*/
|
||||||
property bool showHighlight: root.isHighlighted || isTemporaryHighlighted
|
property bool showHighlight: root.isHighlighted || isTemporaryHighlighted
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Whether the message should temporarily be highlighted.
|
|
||||||
*
|
|
||||||
* Normally triggered when jumping to the event in the timeline, e.g. when a reply
|
|
||||||
* is clicked.
|
|
||||||
*/
|
|
||||||
property bool isTemporaryHighlighted: false
|
|
||||||
|
|
||||||
onIsTemporaryHighlightedChanged: if (isTemporaryHighlighted) {
|
|
||||||
temporaryHighlightTimer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: temporaryHighlightTimer
|
|
||||||
|
|
||||||
interval: 1500
|
|
||||||
onTriggered: isTemporaryHighlighted = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The width available to the bubble content.
|
|
||||||
*/
|
|
||||||
property real contentMaxWidth: (root.isThreaded ? bubbleSizeHelper.parentWidth : bubbleSizeHelper.availableWidth) - bubble.leftPadding - bubble.rightPadding
|
|
||||||
|
|
||||||
Message.room: root.room
|
Message.room: root.room
|
||||||
Message.timeline: root.ListView.view
|
Message.timeline: root.ListView.view
|
||||||
Message.contentModel: root.contentModel
|
Message.contentModel: root.contentModel
|
||||||
Message.index: root.index
|
Message.index: root.index
|
||||||
Message.maxContentWidth: contentMaxWidth
|
Message.maxContentWidth: maxContentWidth - bubble.leftPadding - bubble.rightPadding
|
||||||
|
|
||||||
width: parent?.width
|
width: parent?.width
|
||||||
rightPadding: NeoChatConfig.compactLayout && root.ListView.view.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
alwaysFillWidth: NeoChatConfig.compactLayout
|
enableAvatars: NeoChatConfig?.showAvatarInTimeline ?? false
|
||||||
|
compactMode: NeoChatConfig?.compactLayout ?? false
|
||||||
|
showLocalMessagesOnRight: NeoChatConfig.showLocalMessagesOnRight
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: Bubble {
|
||||||
spacing: Kirigami.Units.smallSpacing
|
id: bubble
|
||||||
|
topPadding: NeoChatConfig.compactLayout ? Kirigami.Units.smallSpacing / 2 : Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: NeoChatConfig.compactLayout ? Kirigami.Units.mediumSpacing / 2 : Kirigami.Units.largeSpacing
|
||||||
|
leftPadding: NeoChatConfig.compactLayout ? 0 : Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
SectionDelegate {
|
author: root.author
|
||||||
id: sectionDelegate
|
showAuthor: root.showAuthor
|
||||||
Layout.fillWidth: true
|
isThreaded: root.isThreaded
|
||||||
visible: root.showSection
|
|
||||||
labelText: root.section
|
contentModel: root.contentModel
|
||||||
colorSet: NeoChatConfig.compactLayout || root.alwaysFillWidth ? Kirigami.Theme.View : Kirigami.Theme.Window
|
|
||||||
|
showHighlight: root.showHighlight
|
||||||
|
|
||||||
|
isPending: root.isPending
|
||||||
|
|
||||||
|
onSelectedTextChanged: selectedText => {
|
||||||
|
root.Message.selectedText = selectedText;
|
||||||
}
|
}
|
||||||
QQC2.ItemDelegate {
|
onHoveredLinkChanged: hoveredLink => {
|
||||||
id: mainContainer
|
root.Message.hoveredLink = hoveredLink;
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: root.showAuthor ? Kirigami.Units.largeSpacing : (NeoChatConfig.compactLayout ? 1 : Kirigami.Units.smallSpacing)
|
|
||||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
|
||||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
implicitHeight: Math.max(root.showAuthor ? avatar.implicitHeight : 0, bubble.height)
|
|
||||||
|
|
||||||
// show hover actions
|
|
||||||
onHoveredChanged: {
|
|
||||||
if (hovered && !Kirigami.Settings.isMobile) {
|
|
||||||
root.setHoverActionsToDelegate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KirigamiComponents.AvatarButton {
|
|
||||||
id: avatar
|
|
||||||
width: visible || NeoChatConfig.showAvatarInTimeline ? Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 : 0
|
|
||||||
height: width
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: Kirigami.Units.smallSpacing
|
|
||||||
top: parent.top
|
|
||||||
topMargin: Kirigami.Units.smallSpacing
|
|
||||||
}
|
|
||||||
|
|
||||||
visible: ((root.showAuthor ?? false) || root.isThreaded) && NeoChatConfig.showAvatarInTimeline && (NeoChatConfig.compactLayout || !_private.showUserMessageOnRight)
|
|
||||||
name: root.author.displayName
|
|
||||||
source: root.author.avatarUrl
|
|
||||||
color: root.author.color
|
|
||||||
asynchronous: true
|
|
||||||
QQC2.ToolTip.text: root.author.htmlSafeDisambiguatedName
|
|
||||||
|
|
||||||
onClicked: RoomManager.resolveResource(root.author.uri)
|
|
||||||
}
|
|
||||||
Bubble {
|
|
||||||
id: bubble
|
|
||||||
anchors.left: avatar.right
|
|
||||||
anchors.leftMargin: Kirigami.Units.largeSpacing
|
|
||||||
anchors.rightMargin: rightPadding
|
|
||||||
|
|
||||||
topPadding: NeoChatConfig.compactLayout ? Kirigami.Units.smallSpacing / 2 : Kirigami.Units.largeSpacing
|
|
||||||
bottomPadding: NeoChatConfig.compactLayout ? Kirigami.Units.mediumSpacing / 2 : Kirigami.Units.largeSpacing
|
|
||||||
leftPadding: NeoChatConfig.compactLayout ? 0 : Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
|
||||||
rightPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
state: _private.showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
|
|
||||||
// states for anchor animations on window resize
|
|
||||||
// as setting anchors to undefined did not work reliably
|
|
||||||
states: [
|
|
||||||
State {
|
|
||||||
name: "userMessageOnRight"
|
|
||||||
AnchorChanges {
|
|
||||||
target: bubble
|
|
||||||
anchors.left: undefined
|
|
||||||
anchors.right: parent.right
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "userMessageOnLeft"
|
|
||||||
AnchorChanges {
|
|
||||||
target: bubble
|
|
||||||
anchors.left: avatar.right
|
|
||||||
anchors.right: root.isThreaded ? parent.right : undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
author: root.author
|
|
||||||
showAuthor: root.showAuthor
|
|
||||||
isThreaded: root.isThreaded
|
|
||||||
|
|
||||||
contentModel: root.contentModel
|
|
||||||
|
|
||||||
showHighlight: root.showHighlight
|
|
||||||
|
|
||||||
isPending: root.isPending
|
|
||||||
|
|
||||||
onSelectedTextChanged: selectedText => {
|
|
||||||
root.Message.selectedText = selectedText;
|
|
||||||
}
|
|
||||||
onHoveredLinkChanged: hoveredLink => {
|
|
||||||
root.Message.hoveredLink = hoveredLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
showBackground: root.cardBackground && !NeoChatConfig.compactLayout
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
visible: mainContainer.hovered && (NeoChatConfig.compactLayout || root.alwaysFillWidth)
|
|
||||||
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
|
||||||
radius: Kirigami.Units.cornerRadius
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onTapped: _private.showMessageMenu()
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
acceptedDevices: PointerDevice.TouchScreen
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onLongPressed: _private.showMessageMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AvatarFlow {
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
||||||
visible: root.showReadMarkers
|
|
||||||
model: root.readMarkers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LibNeoChat.DelegateSizeHelper {
|
showBackground: root.cardBackground && !NeoChatConfig.compactLayout
|
||||||
id: bubbleSizeHelper
|
|
||||||
parentItem: mainContainer
|
TapHandler {
|
||||||
leftPadding: avatar.anchors.leftMargin + (NeoChatConfig.showAvatarInTimeline ? avatar.width + bubble.anchors.leftMargin : 0)
|
acceptedButtons: Qt.RightButton
|
||||||
startBreakpoint: Kirigami.Units.gridUnit * 25
|
onTapped: _private.showMessageMenu()
|
||||||
endBreakpoint: Kirigami.Units.gridUnit * 40
|
}
|
||||||
startPercentWidth: root.alwaysFillWidth ? 100 : 90
|
|
||||||
endPercentWidth: root.alwaysFillWidth ? 100 : 60
|
TapHandler {
|
||||||
|
acceptedDevices: PointerDevice.TouchScreen
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: _private.showMessageMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVisibleInTimeline() {
|
avatarComponent: KirigamiComponents.AvatarButton {
|
||||||
let yoff = Math.round(y - ListView.view.contentY);
|
id: avatar
|
||||||
return (yoff + height > 0 && yoff < ListView.view.height);
|
implicitWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
implicitHeight: width
|
||||||
|
|
||||||
|
name: root.author.displayName
|
||||||
|
source: root.author.avatarUrl
|
||||||
|
color: root.author.color
|
||||||
|
asynchronous: true
|
||||||
|
QQC2.ToolTip.text: root.author.htmlSafeDisambiguatedName
|
||||||
|
|
||||||
|
onClicked: RoomManager.resolveResource(root.author.uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionComponent: SectionDelegate {
|
||||||
|
id: sectionDelegate
|
||||||
|
labelText: root.section
|
||||||
|
colorSet: root.compactMode ? Kirigami.Theme.View : Kirigami.Theme.Window
|
||||||
|
}
|
||||||
|
|
||||||
|
readMarkerComponent: AvatarFlow {
|
||||||
|
model: root.readMarkers
|
||||||
|
}
|
||||||
|
|
||||||
|
compactBackgroundComponent: Rectangle {
|
||||||
|
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
||||||
|
radius: Kirigami.Units.cornerRadius
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHoverActionsToDelegate() {
|
function setHoverActionsToDelegate() {
|
||||||
@@ -348,11 +223,6 @@ TimelineDelegate {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: _private
|
id: _private
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Whether local user messages should be aligned right.
|
|
||||||
*/
|
|
||||||
property bool showUserMessageOnRight: NeoChatConfig.showLocalMessagesOnRight && root.author.isLocalMember && !NeoChatConfig.compactLayout && !root.alwaysFillWidth && !root.isThreaded
|
|
||||||
|
|
||||||
function showMessageMenu() {
|
function showMessageMenu() {
|
||||||
RoomManager.viewEventMenu(root.eventId, root.room, root.author, root.Message.selectedText, root.Message.hoveredLink);
|
RoomManager.viewEventMenu(root.eventId, root.room, root.author, root.Message.selectedText, root.Message.hoveredLink);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import org.kde.kirigami as Kirigami
|
|||||||
import org.kde.kirigamiaddons.delegates as Delegates
|
import org.kde.kirigamiaddons.delegates as Delegates
|
||||||
import org.kde.kirigamiaddons.formcard as FormCard
|
import org.kde.kirigamiaddons.formcard as FormCard
|
||||||
|
|
||||||
import Quotient
|
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ QQC2.ScrollView {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
width: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.contentItem.width : 0
|
width: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.timelineWidth : 0
|
||||||
labelText: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.ListView.section : ""
|
labelText: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.ListView.section : ""
|
||||||
colorSet: NeoChatConfig.compactLayout ? Kirigami.Theme.View : Kirigami.Theme.Window
|
colorSet: NeoChatConfig.compactLayout ? Kirigami.Theme.View : Kirigami.Theme.Window
|
||||||
}
|
}
|
||||||
|
|||||||
590
src/timeline/messagedelegate.cpp
Normal file
590
src/timeline/messagedelegate.cpp
Normal file
@@ -0,0 +1,590 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include "messagedelegate.h"
|
||||||
|
#include "timelinedelegate.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
MessageObjectIncubator::MessageObjectIncubator(std::function<void(QQuickItem *)> initialCallback,
|
||||||
|
std::function<void(MessageObjectIncubator *)> completedCallback,
|
||||||
|
std::function<void(MessageObjectIncubator *)> errorCallback)
|
||||||
|
: QQmlIncubator(QQmlIncubator::Asynchronous)
|
||||||
|
, m_initialCallback(initialCallback)
|
||||||
|
, m_completedCallback(completedCallback)
|
||||||
|
, m_errorCallback(errorCallback)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageObjectIncubator::setInitialState(QObject *object)
|
||||||
|
{
|
||||||
|
auto item = qobject_cast<QQuickItem *>(object);
|
||||||
|
if (item) {
|
||||||
|
m_initialCallback(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageObjectIncubator::statusChanged(QQmlIncubator::Status status)
|
||||||
|
{
|
||||||
|
if (status == QQmlIncubator::Error && m_errorCallback) {
|
||||||
|
m_errorCallback(this);
|
||||||
|
}
|
||||||
|
if (status == QQmlIncubator::Ready && m_completedCallback) {
|
||||||
|
m_completedCallback(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageDelegateBase::MessageDelegateBase(QQuickItem *parent)
|
||||||
|
: TimelineDelegate(parent)
|
||||||
|
{
|
||||||
|
m_contentSizeHelper.setParentItem(this);
|
||||||
|
setPercentageValues();
|
||||||
|
|
||||||
|
connect(this, &MessageDelegateBase::leftPaddingChanged, this, &MessageDelegateBase::setContentPadding);
|
||||||
|
connect(this, &MessageDelegateBase::rightPaddingChanged, this, &MessageDelegateBase::setContentPadding);
|
||||||
|
connect(&m_contentSizeHelper, &DelegateSizeHelper::availableWidthChanged, this, &MessageDelegateBase::setContentPadding);
|
||||||
|
connect(&m_contentSizeHelper, &DelegateSizeHelper::availableWidthChanged, this, &MessageDelegateBase::maxContentWidthChanged);
|
||||||
|
connect(&m_contentSizeHelper, &DelegateSizeHelper::availableWidthChanged, this, &MessageDelegateBase::markAsDirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
NeochatRoomMember *MessageDelegateBase::author() const
|
||||||
|
{
|
||||||
|
return m_author;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setAuthor(NeochatRoomMember *author)
|
||||||
|
{
|
||||||
|
if (author == m_author) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_author = author;
|
||||||
|
Q_EMIT authorChanged();
|
||||||
|
|
||||||
|
setContentPadding();
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::isThreaded() const
|
||||||
|
{
|
||||||
|
return m_isThreaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setIsThreaded(bool isThreaded)
|
||||||
|
{
|
||||||
|
if (isThreaded == m_isThreaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_isThreaded = isThreaded;
|
||||||
|
setAlwaysFillWidth(m_isThreaded || m_compactMode);
|
||||||
|
setPercentageValues(m_isThreaded || m_compactMode);
|
||||||
|
Q_EMIT isThreadedChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setCurveValues()
|
||||||
|
{
|
||||||
|
m_spacing = qreal(m_units->smallSpacing());
|
||||||
|
m_avatarSize = qreal(m_units->gridUnit() + m_units->largeSpacing() * 2);
|
||||||
|
|
||||||
|
m_sizeHelper.setLeftPadding(qreal(m_units->largeSpacing()));
|
||||||
|
setBaseRightPadding();
|
||||||
|
|
||||||
|
m_sizeHelper.setStartBreakpoint(qreal(m_units->gridUnit() * 46));
|
||||||
|
m_sizeHelper.setEndBreakpoint(qreal(m_units->gridUnit() * 66));
|
||||||
|
m_sizeHelper.setMaxWidth(qreal(m_units->gridUnit() * 60));
|
||||||
|
|
||||||
|
m_contentSizeHelper.setStartBreakpoint(qreal(m_units->gridUnit() * 25));
|
||||||
|
m_contentSizeHelper.setEndBreakpoint(qreal(m_units->gridUnit() * 40));
|
||||||
|
m_contentSizeHelper.setMaxWidth(qreal(m_units->gridUnit() * 60));
|
||||||
|
|
||||||
|
setContentPadding();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setBaseRightPadding()
|
||||||
|
{
|
||||||
|
if (!m_units) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_compactMode && width() > m_units->gridUnit() * 30) {
|
||||||
|
m_sizeHelper.setRightPadding(qreal(m_units->gridUnit() * 2 + m_units->largeSpacing()));
|
||||||
|
} else {
|
||||||
|
m_sizeHelper.setRightPadding(qreal(m_units->largeSpacing()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setPercentageValues(bool fillWidth)
|
||||||
|
{
|
||||||
|
if (fillWidth) {
|
||||||
|
m_contentSizeHelper.setStartPercentWidth(100);
|
||||||
|
m_contentSizeHelper.setEndPercentWidth(100);
|
||||||
|
} else {
|
||||||
|
m_contentSizeHelper.setStartPercentWidth(90);
|
||||||
|
m_contentSizeHelper.setEndPercentWidth(60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setContentPadding()
|
||||||
|
{
|
||||||
|
m_contentSizeHelper.setLeftPadding(m_sizeHelper.leftX() + (leaveAvatarSpace() ? m_avatarSize + m_spacing : 0));
|
||||||
|
m_contentSizeHelper.setRightPadding(m_sizeHelper.rightPadding());
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal MessageDelegateBase::maxContentWidth() const
|
||||||
|
{
|
||||||
|
return m_isThreaded || m_alwaysFillWidth ? m_contentSizeHelper.maxAvailableWidth() : m_contentSizeHelper.availableWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::cleanupIncubator(MessageObjectIncubator *incubator)
|
||||||
|
{
|
||||||
|
incubator->clear();
|
||||||
|
delete incubator;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::cleanupItem(QQuickItem *item)
|
||||||
|
{
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item->setParentItem(nullptr);
|
||||||
|
item->disconnect(this);
|
||||||
|
item->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlComponent *MessageDelegateBase::avatarComponent() const
|
||||||
|
{
|
||||||
|
return m_avatarComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setAvatarComponent(QQmlComponent *avatarComponent)
|
||||||
|
{
|
||||||
|
if (avatarComponent == m_avatarComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_avatarComponent = avatarComponent;
|
||||||
|
Q_EMIT avatarComponentChanged();
|
||||||
|
|
||||||
|
updateAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::showAuthor() const
|
||||||
|
{
|
||||||
|
return m_showAuthor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setShowAuthor(bool showAuthor)
|
||||||
|
{
|
||||||
|
if (showAuthor == m_showAuthor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_showAuthor = showAuthor;
|
||||||
|
Q_EMIT showAuthorChanged();
|
||||||
|
|
||||||
|
updateAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::enableAvatars() const
|
||||||
|
{
|
||||||
|
return m_enableAvatars;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setEnableAvatars(bool enableAvatars)
|
||||||
|
{
|
||||||
|
if (enableAvatars == m_enableAvatars) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_enableAvatars = enableAvatars;
|
||||||
|
Q_EMIT enableAvatarsChanged();
|
||||||
|
|
||||||
|
updateAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::leaveAvatarSpace() const
|
||||||
|
{
|
||||||
|
return m_enableAvatars && !showMessageOnRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::showAvatar() const
|
||||||
|
{
|
||||||
|
return m_enableAvatars && m_showAuthor && !showMessageOnRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::updateAvatar()
|
||||||
|
{
|
||||||
|
if (m_avatarComponent && showAvatar() && !m_avatarItem && !m_avatarIncubating) {
|
||||||
|
const auto avatarIncubator = new MessageObjectIncubator(
|
||||||
|
m_objectInitialCallback,
|
||||||
|
[this](MessageObjectIncubator *incubator) {
|
||||||
|
if (!incubator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto avatarObject = qobject_cast<QQuickItem *>(incubator->object());
|
||||||
|
if (avatarObject) {
|
||||||
|
// The setting may have changed during the incubation period.
|
||||||
|
if (showAvatar()) {
|
||||||
|
m_avatarItem = avatarObject;
|
||||||
|
} else {
|
||||||
|
cleanupItem(avatarObject);
|
||||||
|
}
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
cleanupIncubator(incubator);
|
||||||
|
m_avatarIncubating = false;
|
||||||
|
},
|
||||||
|
m_errorCallback);
|
||||||
|
m_avatarComponent->create(*avatarIncubator, qmlContext(m_avatarComponent));
|
||||||
|
m_avatarIncubating = true;
|
||||||
|
} else if (!showAvatar() && m_avatarItem) {
|
||||||
|
cleanupItem(m_avatarItem);
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlComponent *MessageDelegateBase::sectionComponent() const
|
||||||
|
{
|
||||||
|
return m_sectionComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setSectionComponent(QQmlComponent *sectionComponent)
|
||||||
|
{
|
||||||
|
if (sectionComponent == m_sectionComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_sectionComponent = sectionComponent;
|
||||||
|
Q_EMIT sectionComponentChanged();
|
||||||
|
|
||||||
|
updateSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::showSection() const
|
||||||
|
{
|
||||||
|
return m_showSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setShowSection(bool showSection)
|
||||||
|
{
|
||||||
|
if (showSection == m_showSection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_showSection = showSection;
|
||||||
|
Q_EMIT showSectionChanged();
|
||||||
|
|
||||||
|
updateSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::updateSection()
|
||||||
|
{
|
||||||
|
if (m_sectionComponent && m_showSection && !m_sectionItem && !m_sectionIncubating) {
|
||||||
|
const auto sectionIncubator = new MessageObjectIncubator(
|
||||||
|
m_objectInitialCallback,
|
||||||
|
[this](MessageObjectIncubator *incubator) {
|
||||||
|
if (!incubator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto sectionObject = qobject_cast<QQuickItem *>(incubator->object());
|
||||||
|
if (sectionObject) {
|
||||||
|
// The setting may have changed during the incubation period.
|
||||||
|
if (m_showSection) {
|
||||||
|
m_sectionItem = sectionObject;
|
||||||
|
} else {
|
||||||
|
cleanupItem(sectionObject);
|
||||||
|
}
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
cleanupIncubator(incubator);
|
||||||
|
m_sectionIncubating = false;
|
||||||
|
},
|
||||||
|
m_errorCallback);
|
||||||
|
m_sectionComponent->create(*sectionIncubator, qmlContext(m_sectionComponent));
|
||||||
|
m_sectionIncubating = true;
|
||||||
|
} else if (!m_showSection && m_sectionItem) {
|
||||||
|
cleanupItem(m_sectionItem);
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlComponent *MessageDelegateBase::readMarkerComponent() const
|
||||||
|
{
|
||||||
|
return m_readMarkerComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setReadMarkerComponent(QQmlComponent *readMarkerComponent)
|
||||||
|
{
|
||||||
|
if (readMarkerComponent == m_readMarkerComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_readMarkerComponent = readMarkerComponent;
|
||||||
|
Q_EMIT readMarkerComponentChanged();
|
||||||
|
|
||||||
|
updateReadMarker();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::showReadMarkers() const
|
||||||
|
{
|
||||||
|
return m_showReadMarkers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setShowReadMarkers(bool showReadMarkers)
|
||||||
|
{
|
||||||
|
if (showReadMarkers == m_showReadMarkers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_showReadMarkers = showReadMarkers;
|
||||||
|
Q_EMIT showReadMarkersChanged();
|
||||||
|
|
||||||
|
updateReadMarker();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::updateReadMarker()
|
||||||
|
{
|
||||||
|
if (m_readMarkerComponent && m_showReadMarkers && !m_readMarkerItem && !m_readMarkerIncubating) {
|
||||||
|
const auto readMarkerIncubator = new MessageObjectIncubator(
|
||||||
|
m_objectInitialCallback,
|
||||||
|
[this](MessageObjectIncubator *incubator) {
|
||||||
|
if (!incubator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto readMarkerObject = qobject_cast<QQuickItem *>(incubator->object());
|
||||||
|
if (readMarkerObject) {
|
||||||
|
if (m_showReadMarkers) {
|
||||||
|
m_readMarkerItem = readMarkerObject;
|
||||||
|
} else {
|
||||||
|
cleanupItem(readMarkerObject);
|
||||||
|
}
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
cleanupIncubator(incubator);
|
||||||
|
m_readMarkerIncubating = false;
|
||||||
|
},
|
||||||
|
m_errorCallback);
|
||||||
|
m_readMarkerComponent->create(*readMarkerIncubator, qmlContext(m_readMarkerComponent));
|
||||||
|
m_readMarkerIncubating = true;
|
||||||
|
} else if (!m_showReadMarkers && m_readMarkerItem) {
|
||||||
|
cleanupItem(m_readMarkerItem);
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlComponent *MessageDelegateBase::compactBackgroundComponent() const
|
||||||
|
{
|
||||||
|
return m_compactBackgroundComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setCompactBackgroundComponentt(QQmlComponent *compactBackgroundComponent)
|
||||||
|
{
|
||||||
|
if (compactBackgroundComponent == m_compactBackgroundComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_compactBackgroundComponent = compactBackgroundComponent;
|
||||||
|
Q_EMIT compactBackgroundComponentChanged();
|
||||||
|
|
||||||
|
updateBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::compactMode() const
|
||||||
|
{
|
||||||
|
return m_compactMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setCompactMode(bool compactMode)
|
||||||
|
{
|
||||||
|
if (compactMode == m_compactMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_compactMode = compactMode;
|
||||||
|
setAlwaysFillWidth(m_isThreaded || m_compactMode);
|
||||||
|
setPercentageValues(m_isThreaded || m_compactMode);
|
||||||
|
setAcceptHoverEvents(m_compactMode);
|
||||||
|
setBaseRightPadding();
|
||||||
|
|
||||||
|
Q_EMIT compactModeChanged();
|
||||||
|
Q_EMIT maxContentWidthChanged();
|
||||||
|
|
||||||
|
updateBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::updateBackground()
|
||||||
|
{
|
||||||
|
if (m_compactBackgroundComponent && m_compactMode && m_hovered && !m_compactBackgroundItem && !m_compactBackgroundIncubating) {
|
||||||
|
const auto compactBackgroundIncubator = new MessageObjectIncubator(
|
||||||
|
m_objectInitialCallback,
|
||||||
|
[this](MessageObjectIncubator *incubator) {
|
||||||
|
if (!incubator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto compactBackgroundObject = qobject_cast<QQuickItem *>(incubator->object());
|
||||||
|
if (compactBackgroundObject) {
|
||||||
|
if (m_compactMode) {
|
||||||
|
m_compactBackgroundItem = compactBackgroundObject;
|
||||||
|
} else {
|
||||||
|
cleanupItem(compactBackgroundObject);
|
||||||
|
}
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
cleanupIncubator(incubator);
|
||||||
|
m_compactBackgroundIncubating = false;
|
||||||
|
},
|
||||||
|
m_errorCallback);
|
||||||
|
m_compactBackgroundComponent->create(*compactBackgroundIncubator, qmlContext(m_compactBackgroundComponent));
|
||||||
|
m_compactBackgroundIncubating = true;
|
||||||
|
} else if (m_compactBackgroundItem && !m_hovered) {
|
||||||
|
cleanupItem(m_compactBackgroundItem);
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::showLocalMessagesOnRight() const
|
||||||
|
{
|
||||||
|
return m_showLocalMessagesOnRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setShowLocalMessagesOnRight(bool showLocalMessagesOnRight)
|
||||||
|
{
|
||||||
|
if (showLocalMessagesOnRight == m_showLocalMessagesOnRight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_showLocalMessagesOnRight = showLocalMessagesOnRight;
|
||||||
|
Q_EMIT showLocalMessagesOnRightChanged();
|
||||||
|
|
||||||
|
setContentPadding();
|
||||||
|
updateAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::updateImplicitHeight()
|
||||||
|
{
|
||||||
|
qreal implicitHeight = 0.0;
|
||||||
|
int numObj = 0;
|
||||||
|
if (m_showSection && m_sectionItem) {
|
||||||
|
implicitHeight += m_sectionItem->implicitHeight();
|
||||||
|
numObj++;
|
||||||
|
}
|
||||||
|
qreal avatarHeight = 0.0;
|
||||||
|
qreal contentHeight = 0.0;
|
||||||
|
if (showAvatar() && m_avatarItem) {
|
||||||
|
m_avatarItem->setImplicitWidth(m_avatarSize);
|
||||||
|
m_avatarItem->setImplicitHeight(m_avatarSize);
|
||||||
|
avatarHeight = m_avatarItem->implicitHeight();
|
||||||
|
}
|
||||||
|
if (m_contentItem) {
|
||||||
|
contentHeight = m_contentItem->implicitHeight();
|
||||||
|
}
|
||||||
|
implicitHeight += std::max(avatarHeight, contentHeight);
|
||||||
|
if (avatarHeight > 0 || contentHeight > 0) {
|
||||||
|
numObj++;
|
||||||
|
}
|
||||||
|
if (m_showReadMarkers && m_readMarkerItem) {
|
||||||
|
implicitHeight += m_readMarkerItem->implicitHeight();
|
||||||
|
numObj++;
|
||||||
|
}
|
||||||
|
implicitHeight += (numObj - 1) * m_spacing;
|
||||||
|
implicitHeight += m_showAuthor ? m_spacing * 2 : m_spacing;
|
||||||
|
implicitHeight = std::ceil(implicitHeight);
|
||||||
|
setImplicitWidth(m_alwaysFillWidth ? m_sizeHelper.maxAvailableWidth() : m_sizeHelper.availableWidth());
|
||||||
|
setImplicitHeight(implicitHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::showMessageOnRight() const
|
||||||
|
{
|
||||||
|
return m_showLocalMessagesOnRight && !m_alwaysFillWidth && m_author && m_author->isLocalMember();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::resizeContent()
|
||||||
|
{
|
||||||
|
if (!isComponentComplete() || m_resizingContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_isDirty = false;
|
||||||
|
m_resizingContent = true;
|
||||||
|
|
||||||
|
updateImplicitHeight();
|
||||||
|
|
||||||
|
qreal nextY = m_showAuthor ? m_spacing * 2 : m_spacing;
|
||||||
|
|
||||||
|
if (m_compactMode && m_compactBackgroundItem) {
|
||||||
|
m_compactBackgroundItem->setPosition(QPointF(std::ceil(m_sizeHelper.leftX() / 2), std::ceil(nextY / 2)));
|
||||||
|
m_compactBackgroundItem->setSize(
|
||||||
|
QSizeF(m_sizeHelper.availableWidth() + m_sizeHelper.rightPadding() - std::ceil(m_sizeHelper.leftPadding() / 2), implicitHeight()));
|
||||||
|
m_compactBackgroundItem->setZ(-1);
|
||||||
|
}
|
||||||
|
if (m_showSection && m_sectionItem) {
|
||||||
|
m_sectionItem->setPosition(QPointF(m_sizeHelper.leftX(), nextY));
|
||||||
|
m_sectionItem->setSize(QSizeF(m_sizeHelper.availableWidth(), m_sectionItem->implicitHeight()));
|
||||||
|
nextY += m_sectionItem->implicitHeight() + m_spacing;
|
||||||
|
}
|
||||||
|
qreal yAdd = 0.0;
|
||||||
|
if (showAvatar() && m_avatarItem) {
|
||||||
|
m_avatarItem->setPosition(QPointF(m_sizeHelper.leftX(), nextY));
|
||||||
|
m_avatarItem->setSize(QSizeF(m_avatarItem->implicitWidth(), m_avatarItem->implicitHeight()));
|
||||||
|
yAdd = m_avatarItem->implicitWidth();
|
||||||
|
}
|
||||||
|
if (m_contentItem) {
|
||||||
|
const auto contentItemWidth =
|
||||||
|
m_alwaysFillWidth ? m_contentSizeHelper.availableWidth() : std::min(m_contentItem->implicitWidth(), m_contentSizeHelper.availableWidth());
|
||||||
|
const auto contentX = showMessageOnRight() ? m_sizeHelper.rightX() - contentItemWidth - 1 : m_contentSizeHelper.leftPadding();
|
||||||
|
m_contentItem->setPosition(QPointF(contentX, nextY));
|
||||||
|
m_contentItem->setSize(QSizeF(contentItemWidth, m_contentItem->implicitHeight()));
|
||||||
|
yAdd = std::max(yAdd, m_contentItem->implicitHeight());
|
||||||
|
}
|
||||||
|
nextY += yAdd + m_spacing;
|
||||||
|
if (m_showReadMarkers && m_readMarkerItem) {
|
||||||
|
qreal extraSpacing = m_readMarkerItem->implicitWidth() < m_sizeHelper.availableWidth() - m_spacing ? m_spacing : 0;
|
||||||
|
qreal objectWidth = std::min(m_sizeHelper.availableWidth(), m_readMarkerItem->implicitWidth());
|
||||||
|
m_readMarkerItem->setPosition(QPointF(m_sizeHelper.rightX() - objectWidth - extraSpacing, nextY));
|
||||||
|
m_readMarkerItem->setSize(QSizeF(objectWidth, m_readMarkerItem->implicitHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_resizingContent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::hoverEnterEvent(QHoverEvent *event)
|
||||||
|
{
|
||||||
|
m_hovered = true;
|
||||||
|
event->setAccepted(true);
|
||||||
|
updateBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::hoverMoveEvent(QHoverEvent *event)
|
||||||
|
{
|
||||||
|
m_hovered = contains(event->pos());
|
||||||
|
event->setAccepted(true);
|
||||||
|
updateBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::hoverLeaveEvent(QHoverEvent *event)
|
||||||
|
{
|
||||||
|
m_hovered = false;
|
||||||
|
event->setAccepted(true);
|
||||||
|
updateBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::isTemporaryHighlighted() const
|
||||||
|
{
|
||||||
|
return m_temporaryHighlightTimer && m_temporaryHighlightTimer->isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setIsTemporaryHighlighted(bool isTemporaryHighlighted)
|
||||||
|
{
|
||||||
|
if (!isTemporaryHighlighted) {
|
||||||
|
if (m_temporaryHighlightTimer) {
|
||||||
|
m_temporaryHighlightTimer->stop();
|
||||||
|
m_temporaryHighlightTimer->deleteLater();
|
||||||
|
Q_EMIT isTemporaryHighlightedChanged();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_temporaryHighlightTimer) {
|
||||||
|
m_temporaryHighlightTimer = new QTimer(this);
|
||||||
|
}
|
||||||
|
m_temporaryHighlightTimer->start(1500);
|
||||||
|
connect(m_temporaryHighlightTimer, &QTimer::timeout, this, [this]() {
|
||||||
|
m_temporaryHighlightTimer->deleteLater();
|
||||||
|
Q_EMIT isTemporaryHighlightedChanged();
|
||||||
|
});
|
||||||
|
Q_EMIT isTemporaryHighlightedChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "moc_messagedelegate.cpp"
|
||||||
240
src/timeline/messagedelegate.h
Normal file
240
src/timeline/messagedelegate.h
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QQmlIncubator>
|
||||||
|
#include <QQuickItem>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "neochatroommember.h"
|
||||||
|
#include "timelinedelegate.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Incubator for creating instances of components as required.
|
||||||
|
*/
|
||||||
|
class MessageObjectIncubator : public QQmlIncubator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MessageObjectIncubator(std::function<void(QQuickItem *)> initialCallback,
|
||||||
|
std::function<void(MessageObjectIncubator *)> completedCallback,
|
||||||
|
std::function<void(MessageObjectIncubator *)> errorCallback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setInitialState(QObject *object) override;
|
||||||
|
std::function<void(QQuickItem *)> m_initialCallback;
|
||||||
|
void statusChanged(QQmlIncubator::Status status) override;
|
||||||
|
std::function<void(MessageObjectIncubator *)> m_completedCallback;
|
||||||
|
std::function<void(MessageObjectIncubator *)> m_errorCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delegate Item for all messages in the timeline.
|
||||||
|
*/
|
||||||
|
class MessageDelegateBase : public TimelineDelegate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The message author.
|
||||||
|
*
|
||||||
|
* @sa NeochatRoomMember
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(NeochatRoomMember *author READ author WRITE setAuthor NOTIFY authorChanged FINAL REQUIRED)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the message is threaded.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool isThreaded READ isThreaded WRITE setIsThreaded NOTIFY isThreadedChanged FINAL REQUIRED)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The maximum width available to the content item.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(qreal maxContentWidth READ maxContentWidth NOTIFY maxContentWidthChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The component to use to visualize a user avatar.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QQmlComponent *avatarComponent READ avatarComponent WRITE setAvatarComponent NOTIFY avatarComponentChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the user avatar should be shown.
|
||||||
|
*
|
||||||
|
* @note An avatar is only shown if enabled.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged FINAL REQUIRED)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether user avatars are enabled.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool enableAvatars READ enableAvatars WRITE setEnableAvatars NOTIFY enableAvatarsChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The component to use to visualize a section.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QQmlComponent *sectionComponent READ sectionComponent WRITE setSectionComponent NOTIFY sectionComponentChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether to show the section component.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool showSection READ showSection WRITE setShowSection NOTIFY showSectionChanged FINAL REQUIRED)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The component to use to visualize other user read markers.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QQmlComponent *readMarkerComponent READ readMarkerComponent WRITE setReadMarkerComponent NOTIFY readMarkerComponentChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether to show the other user read markers.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool showReadMarkers READ showReadMarkers WRITE setShowReadMarkers NOTIFY showReadMarkersChanged FINAL REQUIRED)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The component to use to visualize the hover state in compact mode.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QQmlComponent *compactBackgroundComponent READ compactBackgroundComponent WRITE setCompactBackgroundComponentt NOTIFY
|
||||||
|
compactBackgroundComponentChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether to use the compact mode appearance.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool compactMode READ compactMode WRITE setCompactMode NOTIFY compactModeChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether to show messages by the local user on the right in bubble mode.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool showLocalMessagesOnRight READ showLocalMessagesOnRight WRITE setShowLocalMessagesOnRight NOTIFY showLocalMessagesOnRightChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the message should be highlighted temporarily.
|
||||||
|
*
|
||||||
|
* Normally triggered when jumping to the event in the timeline, e.g. when a reply
|
||||||
|
* is clicked.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool isTemporaryHighlighted READ isTemporaryHighlighted WRITE setIsTemporaryHighlighted NOTIFY isTemporaryHighlightedChanged FINAL)
|
||||||
|
|
||||||
|
public:
|
||||||
|
MessageDelegateBase(QQuickItem *parent = nullptr);
|
||||||
|
|
||||||
|
NeochatRoomMember *author() const;
|
||||||
|
void setAuthor(NeochatRoomMember *author);
|
||||||
|
|
||||||
|
bool isThreaded() const;
|
||||||
|
void setIsThreaded(bool isThreaded);
|
||||||
|
|
||||||
|
qreal maxContentWidth() const;
|
||||||
|
|
||||||
|
QQmlComponent *avatarComponent() const;
|
||||||
|
void setAvatarComponent(QQmlComponent *avatarComponent);
|
||||||
|
bool showAuthor() const;
|
||||||
|
void setShowAuthor(bool showAuthor);
|
||||||
|
bool enableAvatars() const;
|
||||||
|
void setEnableAvatars(bool enableAvatars);
|
||||||
|
|
||||||
|
QQmlComponent *sectionComponent() const;
|
||||||
|
void setSectionComponent(QQmlComponent *sectionComponent);
|
||||||
|
bool showSection() const;
|
||||||
|
void setShowSection(bool showSection);
|
||||||
|
|
||||||
|
QQmlComponent *readMarkerComponent() const;
|
||||||
|
void setReadMarkerComponent(QQmlComponent *readMarkerComponent);
|
||||||
|
bool showReadMarkers() const;
|
||||||
|
void setShowReadMarkers(bool showReadMarkers);
|
||||||
|
|
||||||
|
QQmlComponent *compactBackgroundComponent() const;
|
||||||
|
void setCompactBackgroundComponentt(QQmlComponent *compactBackgroundComponent);
|
||||||
|
bool compactMode() const;
|
||||||
|
void setCompactMode(bool compactMode);
|
||||||
|
|
||||||
|
bool showLocalMessagesOnRight() const;
|
||||||
|
void setShowLocalMessagesOnRight(bool showLocalMessagesOnRight);
|
||||||
|
|
||||||
|
bool isTemporaryHighlighted() const;
|
||||||
|
void setIsTemporaryHighlighted(bool isTemporaryHighlighted);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void authorChanged();
|
||||||
|
void isThreadedChanged();
|
||||||
|
void maxContentWidthChanged();
|
||||||
|
void avatarComponentChanged();
|
||||||
|
void showAuthorChanged();
|
||||||
|
void enableAvatarsChanged();
|
||||||
|
void sectionComponentChanged();
|
||||||
|
void showSectionChanged();
|
||||||
|
void readMarkerComponentChanged();
|
||||||
|
void showReadMarkersChanged();
|
||||||
|
void compactBackgroundComponentChanged();
|
||||||
|
void compactModeChanged();
|
||||||
|
void showLocalMessagesOnRightChanged();
|
||||||
|
void isTemporaryHighlightedChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
DelegateSizeHelper m_contentSizeHelper;
|
||||||
|
|
||||||
|
QPointer<NeochatRoomMember> m_author;
|
||||||
|
|
||||||
|
bool m_isThreaded = false;
|
||||||
|
|
||||||
|
QPointer<QQmlComponent> m_avatarComponent;
|
||||||
|
bool m_avatarIncubating = false;
|
||||||
|
QPointer<QQuickItem> m_avatarItem;
|
||||||
|
bool m_showAuthor = false;
|
||||||
|
bool m_enableAvatars = true;
|
||||||
|
bool leaveAvatarSpace() const;
|
||||||
|
bool showAvatar() const;
|
||||||
|
void updateAvatar();
|
||||||
|
|
||||||
|
QPointer<QQmlComponent> m_sectionComponent;
|
||||||
|
bool m_sectionIncubating = false;
|
||||||
|
QPointer<QQuickItem> m_sectionItem;
|
||||||
|
bool m_showSection = false;
|
||||||
|
void updateSection();
|
||||||
|
|
||||||
|
QPointer<QQmlComponent> m_readMarkerComponent;
|
||||||
|
bool m_readMarkerIncubating = false;
|
||||||
|
QPointer<QQuickItem> m_readMarkerItem;
|
||||||
|
bool m_showReadMarkers = false;
|
||||||
|
void updateReadMarker();
|
||||||
|
|
||||||
|
QPointer<QQmlComponent> m_compactBackgroundComponent;
|
||||||
|
bool m_compactBackgroundIncubating = false;
|
||||||
|
QPointer<QQuickItem> m_compactBackgroundItem;
|
||||||
|
bool m_compactMode = false;
|
||||||
|
void updateBackground();
|
||||||
|
|
||||||
|
bool m_showLocalMessagesOnRight = true;
|
||||||
|
|
||||||
|
bool m_hovered = false;
|
||||||
|
void hoverEnterEvent(QHoverEvent *event) override;
|
||||||
|
void hoverMoveEvent(QHoverEvent *event) override;
|
||||||
|
void hoverLeaveEvent(QHoverEvent *event) override;
|
||||||
|
|
||||||
|
std::function<void(QQuickItem *)> m_objectInitialCallback = [this](QQuickItem *object) {
|
||||||
|
object->setParentItem(this);
|
||||||
|
connect(object, &QQuickItem::implicitWidthChanged, this, &MessageDelegateBase::markAsDirty);
|
||||||
|
connect(object, &QQuickItem::implicitHeightChanged, this, &MessageDelegateBase::markAsDirty);
|
||||||
|
connect(object, &QQuickItem::visibleChanged, this, &MessageDelegateBase::markAsDirty);
|
||||||
|
};
|
||||||
|
std::function<void(MessageObjectIncubator *)> m_errorCallback = [this](MessageObjectIncubator *incubator) {
|
||||||
|
if (incubator->object()) {
|
||||||
|
incubator->object()->deleteLater();
|
||||||
|
}
|
||||||
|
cleanupIncubator(incubator);
|
||||||
|
};
|
||||||
|
void cleanupIncubator(MessageObjectIncubator *incubator);
|
||||||
|
void cleanupItem(QQuickItem *item);
|
||||||
|
|
||||||
|
qreal m_spacing = 0.0;
|
||||||
|
qreal m_avatarSize = 0.0;
|
||||||
|
|
||||||
|
void setCurveValues() override;
|
||||||
|
void setBaseRightPadding();
|
||||||
|
void setPercentageValues(bool fillWidth = false);
|
||||||
|
void setContentPadding();
|
||||||
|
void updateImplicitHeight() override;
|
||||||
|
bool showMessageOnRight() const;
|
||||||
|
void resizeContent() override;
|
||||||
|
|
||||||
|
QPointer<QTimer> m_temporaryHighlightTimer;
|
||||||
|
};
|
||||||
@@ -9,13 +9,17 @@ TimelineDelegate::TimelineDelegate(QQuickItem *parent)
|
|||||||
m_sizeHelper.setParentItem(this);
|
m_sizeHelper.setParentItem(this);
|
||||||
connect(&m_sizeHelper, &DelegateSizeHelper::leftPaddingChanged, this, [this]() {
|
connect(&m_sizeHelper, &DelegateSizeHelper::leftPaddingChanged, this, [this]() {
|
||||||
Q_EMIT leftPaddingChanged();
|
Q_EMIT leftPaddingChanged();
|
||||||
resizeContent();
|
Q_EMIT timelineWidthChanged();
|
||||||
updatePolish();
|
markAsDirty();
|
||||||
});
|
});
|
||||||
connect(&m_sizeHelper, &DelegateSizeHelper::rightPaddingChanged, this, [this]() {
|
connect(&m_sizeHelper, &DelegateSizeHelper::rightPaddingChanged, this, [this]() {
|
||||||
Q_EMIT rightPaddingChanged();
|
Q_EMIT rightPaddingChanged();
|
||||||
resizeContent();
|
Q_EMIT timelineWidthChanged();
|
||||||
updatePolish();
|
markAsDirty();
|
||||||
|
});
|
||||||
|
connect(&m_sizeHelper, &DelegateSizeHelper::availableWidthChanged, this, [this]() {
|
||||||
|
Q_EMIT timelineWidthChanged();
|
||||||
|
markAsDirty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +35,7 @@ void TimelineDelegate::setContentItem(QQuickItem *item)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_contentItem) {
|
if (m_contentItem) {
|
||||||
disconnect(m_contentItem, &QQuickItem::implicitHeightChanged, this, &TimelineDelegate::updateImplicitHeight);
|
m_contentItem->disconnect(this);
|
||||||
m_contentItem->setParentItem(nullptr);
|
m_contentItem->setParentItem(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,13 +43,13 @@ void TimelineDelegate::setContentItem(QQuickItem *item)
|
|||||||
|
|
||||||
if (m_contentItem) {
|
if (m_contentItem) {
|
||||||
m_contentItem->setParentItem(this);
|
m_contentItem->setParentItem(this);
|
||||||
connect(m_contentItem, &QQuickItem::implicitHeightChanged, this, &TimelineDelegate::updateImplicitHeight);
|
connect(m_contentItem, &QQuickItem::implicitWidthChanged, this, &TimelineDelegate::markAsDirty);
|
||||||
|
connect(m_contentItem, &QQuickItem::implicitHeightChanged, this, &TimelineDelegate::markAsDirty);
|
||||||
|
connect(m_contentItem, &QQuickItem::visibleChanged, this, &TimelineDelegate::markAsDirty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markAsDirty();
|
||||||
Q_EMIT contentItemChanged();
|
Q_EMIT contentItemChanged();
|
||||||
|
|
||||||
updateImplicitHeight();
|
|
||||||
resizeContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TimelineDelegate::alwaysFillWidth()
|
bool TimelineDelegate::alwaysFillWidth()
|
||||||
@@ -59,10 +63,16 @@ void TimelineDelegate::setAlwaysFillWidth(bool alwaysFillWidth)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_alwaysFillWidth = alwaysFillWidth;
|
m_alwaysFillWidth = alwaysFillWidth;
|
||||||
|
|
||||||
|
if (m_alwaysFillWidth) {
|
||||||
|
m_sizeHelper.setEndPercentWidth(100);
|
||||||
|
} else {
|
||||||
|
m_sizeHelper.setEndPercentWidth(85);
|
||||||
|
}
|
||||||
|
|
||||||
Q_EMIT alwaysFillWidthChanged();
|
Q_EMIT alwaysFillWidthChanged();
|
||||||
|
|
||||||
resizeContent();
|
markAsDirty();
|
||||||
updatePolish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal TimelineDelegate::leftPadding()
|
qreal TimelineDelegate::leftPadding()
|
||||||
@@ -85,14 +95,18 @@ void TimelineDelegate::setRightPadding(qreal rightPadding)
|
|||||||
m_sizeHelper.setRightPadding(rightPadding);
|
m_sizeHelper.setRightPadding(rightPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qreal TimelineDelegate::timelineWidth()
|
||||||
|
{
|
||||||
|
return m_sizeHelper.availableWidth();
|
||||||
|
}
|
||||||
|
|
||||||
void TimelineDelegate::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
void TimelineDelegate::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||||
{
|
{
|
||||||
if (newGeometry == oldGeometry) {
|
if (newGeometry != oldGeometry) {
|
||||||
return;
|
markAsDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||||
resizeContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineDelegate::componentComplete()
|
void TimelineDelegate::componentComplete()
|
||||||
@@ -103,8 +117,14 @@ void TimelineDelegate::componentComplete()
|
|||||||
Q_ASSERT(engine);
|
Q_ASSERT(engine);
|
||||||
m_units = engine->singletonInstance<Kirigami::Platform::Units *>("org.kde.kirigami.platform", "Units");
|
m_units = engine->singletonInstance<Kirigami::Platform::Units *>("org.kde.kirigami.platform", "Units");
|
||||||
Q_ASSERT(m_units);
|
Q_ASSERT(m_units);
|
||||||
setCurveValues();
|
|
||||||
connect(m_units, &Kirigami::Platform::Units::gridUnitChanged, this, &TimelineDelegate::setCurveValues);
|
connect(m_units, &Kirigami::Platform::Units::gridUnitChanged, this, &TimelineDelegate::setCurveValues);
|
||||||
|
connect(m_units, &Kirigami::Platform::Units::largeSpacingChanged, this, &TimelineDelegate::setCurveValues);
|
||||||
|
connect(m_units, &Kirigami::Platform::Units::smallSpacingChanged, this, &TimelineDelegate::setCurveValues);
|
||||||
|
setCurveValues();
|
||||||
|
|
||||||
|
if (m_isDirty) {
|
||||||
|
resizeContent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineDelegate::setCurveValues()
|
void TimelineDelegate::setCurveValues()
|
||||||
@@ -115,21 +135,38 @@ void TimelineDelegate::setCurveValues()
|
|||||||
m_sizeHelper.setStartBreakpoint(qreal(m_units->gridUnit() * 46));
|
m_sizeHelper.setStartBreakpoint(qreal(m_units->gridUnit() * 46));
|
||||||
m_sizeHelper.setEndBreakpoint(qreal(m_units->gridUnit() * 66));
|
m_sizeHelper.setEndBreakpoint(qreal(m_units->gridUnit() * 66));
|
||||||
m_sizeHelper.setMaxWidth(qreal(m_units->gridUnit() * 60));
|
m_sizeHelper.setMaxWidth(qreal(m_units->gridUnit() * 60));
|
||||||
|
}
|
||||||
|
|
||||||
resizeContent();
|
void TimelineDelegate::markAsDirty()
|
||||||
|
{
|
||||||
|
if (!m_isDirty) {
|
||||||
|
m_isDirty = true;
|
||||||
|
polish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimelineDelegate::updatePolish()
|
||||||
|
{
|
||||||
|
if (m_isDirty) {
|
||||||
|
resizeContent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineDelegate::resizeContent()
|
void TimelineDelegate::resizeContent()
|
||||||
{
|
{
|
||||||
if (m_contentItem == nullptr || !isComponentComplete()) {
|
if (m_contentItem == nullptr || !isComponentComplete() || m_resizingContent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto availableWidth = m_alwaysFillWidth ? m_sizeHelper.maxAvailableWidth() : m_sizeHelper.availableWidth();
|
m_isDirty = false;
|
||||||
|
m_resizingContent = true;
|
||||||
|
|
||||||
const auto leftPadding = m_sizeHelper.leftPadding() + (m_sizeHelper.maxAvailableWidth() - availableWidth) / 2;
|
updateImplicitHeight();
|
||||||
m_contentItem->setPosition(QPointF(leftPadding, 0));
|
|
||||||
m_contentItem->setSize(QSizeF(availableWidth, m_contentItem->implicitHeight()));
|
m_contentItem->setPosition(QPointF(m_sizeHelper.leftX(), 0));
|
||||||
|
m_contentItem->setSize(QSizeF(m_sizeHelper.availableWidth(), m_contentItem->implicitHeight()));
|
||||||
|
|
||||||
|
m_resizingContent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineDelegate::updateImplicitHeight()
|
void TimelineDelegate::updateImplicitHeight()
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ class TimelineDelegate : public QQuickItem
|
|||||||
*/
|
*/
|
||||||
Q_PROPERTY(qreal rightPadding READ rightPadding WRITE setRightPadding NOTIFY rightPaddingChanged FINAL)
|
Q_PROPERTY(qreal rightPadding READ rightPadding WRITE setRightPadding NOTIFY rightPaddingChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The width of the timeline column.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(qreal timelineWidth READ timelineWidth NOTIFY timelineWidthChanged FINAL)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TimelineDelegate(QQuickItem *parent = nullptr);
|
TimelineDelegate(QQuickItem *parent = nullptr);
|
||||||
|
|
||||||
@@ -65,28 +70,34 @@ public:
|
|||||||
[[nodiscard]] qreal rightPadding();
|
[[nodiscard]] qreal rightPadding();
|
||||||
void setRightPadding(qreal rightPadding);
|
void setRightPadding(qreal rightPadding);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal timelineWidth();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void contentItemChanged();
|
void contentItemChanged();
|
||||||
void alwaysFillWidthChanged();
|
void alwaysFillWidthChanged();
|
||||||
void leftPaddingChanged();
|
void leftPaddingChanged();
|
||||||
void rightPaddingChanged();
|
void rightPaddingChanged();
|
||||||
|
void timelineWidthChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
|
||||||
void componentComplete() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Kirigami::Platform::Units *m_units = nullptr;
|
Kirigami::Platform::Units *m_units = nullptr;
|
||||||
void setCurveValues();
|
virtual void setCurveValues();
|
||||||
|
|
||||||
DelegateSizeHelper m_sizeHelper;
|
DelegateSizeHelper m_sizeHelper;
|
||||||
|
|
||||||
bool m_alwaysFillWidth = false;
|
bool m_alwaysFillWidth = false;
|
||||||
|
|
||||||
void resizeContent();
|
|
||||||
void updateImplicitHeight();
|
|
||||||
|
|
||||||
QPointer<QQuickItem> m_contentItem;
|
QPointer<QQuickItem> m_contentItem;
|
||||||
|
|
||||||
|
void markAsDirty();
|
||||||
|
bool m_isDirty = false;
|
||||||
|
virtual void updateImplicitHeight();
|
||||||
|
virtual void resizeContent();
|
||||||
|
bool m_resizingContent = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void componentComplete() override;
|
||||||
|
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||||
|
void updatePolish() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user