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::QuickControls2
|
||||
Qt::Widgets
|
||||
KF6::I18n
|
||||
KF6::I18nQml
|
||||
KF6::Kirigami
|
||||
QuotientQt6
|
||||
LibNeoChat
|
||||
|
||||
@@ -23,6 +23,7 @@ QQC2.ApplicationWindow {
|
||||
height: root.height
|
||||
|
||||
contentItem: ListView {
|
||||
cacheBuffer: 1000000
|
||||
model: messageFilterModel
|
||||
|
||||
delegate: EventDelegate {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
|
||||
#include <KLocalizedQmlContext>
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "memtesttimelinemodel.h"
|
||||
@@ -19,12 +20,15 @@ int main(int argc, char **argv)
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
|
||||
|
||||
KLocalization::setupLocalizedContext(&engine);
|
||||
|
||||
MemTestTimelineModel *memTestTimelineModel = new MemTestTimelineModel;
|
||||
MessageFilterModel *messageFilterModel = new MessageFilterModel(nullptr, memTestTimelineModel);
|
||||
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
|
||||
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
|
||||
|
||||
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@
|
||||
"formatted_body": "This is an example<br>text message",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1000000000000:example.org",
|
||||
"event_id": 0,
|
||||
"origin_server_ts": 1000000000000,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
@@ -255,7 +255,7 @@
|
||||
"body": "This is a highlight @bob:example.org",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1000000000001:example.org",
|
||||
"event_id": 1,
|
||||
"origin_server_ts": 1000000000001,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
@@ -266,11 +266,11 @@
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"m.relates_to": {
|
||||
"event_id": "$1000000000001:example.org",
|
||||
"m.relates_to": {
|
||||
"event_id": 1,
|
||||
"key": "👍",
|
||||
"rel_type": "m.annotation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"origin_server_ts": 1000000000002,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
@@ -279,7 +279,7 @@
|
||||
"unsigned": {
|
||||
"age": 390159120
|
||||
},
|
||||
"event_id": "$1000000000002:example.org",
|
||||
"event_id": 2,
|
||||
"age": 390159120
|
||||
},
|
||||
{
|
||||
@@ -289,7 +289,7 @@
|
||||
"formatted_body": "reply",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$1000000000000:example.org"
|
||||
"event_id": 0
|
||||
}
|
||||
},
|
||||
"msgtype": "m.text"
|
||||
@@ -300,7 +300,7 @@
|
||||
"unsigned": {
|
||||
"age": 98
|
||||
},
|
||||
"event_id": "$1000000000003:example.org",
|
||||
"event_id": 3,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
|
||||
},
|
||||
{
|
||||
@@ -317,7 +317,7 @@
|
||||
"uri": "geo:51.7035,-1.14394"
|
||||
}
|
||||
},
|
||||
"event_id": "$1000000000004:example.org",
|
||||
"event_id": 4,
|
||||
"origin_server_ts": 1000000000004,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU: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>",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1000000000005:example.org",
|
||||
"event_id": 5,
|
||||
"origin_server_ts": 1000000000005,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU: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. ",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1000000000006:example.org",
|
||||
"event_id": 6,
|
||||
"origin_server_ts": 1000000000006,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU: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>",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$1000000000007:example.org",
|
||||
"event_id": 7,
|
||||
"origin_server_ts": 1000000000007,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
@@ -371,282 +371,6 @@
|
||||
"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": "$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,
|
||||
|
||||
@@ -39,10 +39,52 @@ public:
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
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);
|
||||
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) {
|
||||
connect(m_parentItem, &QQuickItem::widthChanged, this, [this]() {
|
||||
Q_EMIT availablePercentageWidthChanged();
|
||||
Q_EMIT availableWidthChanged();
|
||||
calcWidthsChanged();
|
||||
});
|
||||
}
|
||||
|
||||
Q_EMIT parentItemChanged();
|
||||
Q_EMIT availablePercentageWidthChanged();
|
||||
Q_EMIT availableWidthChanged();
|
||||
calcWidthsChanged();
|
||||
}
|
||||
|
||||
qreal DelegateSizeHelper::leftPadding() const
|
||||
@@ -48,8 +46,7 @@ void DelegateSizeHelper::setLeftPadding(qreal leftPadding)
|
||||
}
|
||||
m_leftPadding = leftPadding;
|
||||
Q_EMIT leftPaddingChanged();
|
||||
Q_EMIT availablePercentageWidthChanged();
|
||||
Q_EMIT availableWidthChanged();
|
||||
calcWidthsChanged();
|
||||
}
|
||||
|
||||
qreal DelegateSizeHelper::rightPadding() const
|
||||
@@ -64,8 +61,7 @@ void DelegateSizeHelper::setRightPadding(qreal rightPadding)
|
||||
}
|
||||
m_rightPadding = rightPadding;
|
||||
Q_EMIT rightPaddingChanged();
|
||||
Q_EMIT availablePercentageWidthChanged();
|
||||
Q_EMIT availableWidthChanged();
|
||||
calcWidthsChanged();
|
||||
}
|
||||
|
||||
qreal DelegateSizeHelper::startBreakpoint() const
|
||||
@@ -80,6 +76,7 @@ void DelegateSizeHelper::setStartBreakpoint(qreal startBreakpoint)
|
||||
}
|
||||
m_startBreakpoint = startBreakpoint;
|
||||
Q_EMIT startBreakpointChanged();
|
||||
calcWidthsChanged();
|
||||
}
|
||||
|
||||
qreal DelegateSizeHelper::endBreakpoint() const
|
||||
@@ -94,6 +91,7 @@ void DelegateSizeHelper::setEndBreakpoint(qreal endBreakpoint)
|
||||
}
|
||||
m_endBreakpoint = endBreakpoint;
|
||||
Q_EMIT endBreakpointChanged();
|
||||
calcWidthsChanged();
|
||||
}
|
||||
|
||||
int DelegateSizeHelper::startPercentWidth() const
|
||||
@@ -108,6 +106,7 @@ void DelegateSizeHelper::setStartPercentWidth(int startPercentWidth)
|
||||
}
|
||||
m_startPercentWidth = startPercentWidth;
|
||||
Q_EMIT startPercentWidthChanged();
|
||||
calcWidthsChanged();
|
||||
}
|
||||
|
||||
int DelegateSizeHelper::endPercentWidth() const
|
||||
@@ -122,6 +121,7 @@ void DelegateSizeHelper::setEndPercentWidth(int endPercentWidth)
|
||||
}
|
||||
m_endPercentWidth = endPercentWidth;
|
||||
Q_EMIT endPercentWidthChanged();
|
||||
calcWidthsChanged();
|
||||
}
|
||||
|
||||
qreal DelegateSizeHelper::maxWidth() const
|
||||
@@ -143,6 +143,7 @@ void DelegateSizeHelper::setMaxWidth(qreal maxWidth)
|
||||
}
|
||||
m_maxWidth = maxWidth;
|
||||
Q_EMIT maxWidthChanged();
|
||||
calcWidthsChanged();
|
||||
}
|
||||
|
||||
qreal DelegateSizeHelper::maxAvailableWidth() const
|
||||
@@ -188,4 +189,20 @@ qreal DelegateSizeHelper::availableWidth() const
|
||||
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"
|
||||
|
||||
@@ -84,7 +84,7 @@ class DelegateSizeHelper : public QObject
|
||||
*
|
||||
* @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.
|
||||
@@ -124,6 +124,16 @@ public:
|
||||
int availablePercentageWidth() 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:
|
||||
void parentItemChanged();
|
||||
void leftPaddingChanged();
|
||||
@@ -148,5 +158,7 @@ private:
|
||||
int m_endPercentWidth = 85;
|
||||
std::optional<qreal> m_maxWidth = std::nullopt;
|
||||
|
||||
void calcWidthsChanged();
|
||||
|
||||
int calculateAvailablePercentageWidth() const;
|
||||
};
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
|
||||
Flow {
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
property var avatarSize: Kirigami.Units.iconSizes.small
|
||||
@@ -33,6 +34,11 @@ Flow {
|
||||
}
|
||||
QQC2.Label {
|
||||
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 !== ""
|
||||
color: Kirigami.Theme.textColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -41,18 +47,17 @@ Flow {
|
||||
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
radius: height / 2
|
||||
shadow.size: Kirigami.Units.smallSpacing
|
||||
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.width: 1
|
||||
radius: Math.ceil(height / 2)
|
||||
shadow {
|
||||
size: Kirigami.Units.smallSpacing
|
||||
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)
|
||||
width: 1
|
||||
}
|
||||
}
|
||||
|
||||
height: Kirigami.Units.iconSizes.small + Kirigami.Units.smallSpacing
|
||||
width: Math.max(excessAvatarsTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height)
|
||||
|
||||
TextMetrics {
|
||||
id: excessAvatarsTextMetrics
|
||||
text: excessAvatarsLabel.text
|
||||
|
||||
@@ -63,6 +63,7 @@ ecm_add_qml_module(Timeline GENERATE_PLUGIN_SOURCE
|
||||
locationhelper.cpp
|
||||
mediasizehelper.cpp
|
||||
messageattached.cpp
|
||||
messagedelegate.cpp
|
||||
pollhandler.cpp
|
||||
timelinedelegate.cpp
|
||||
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
|
||||
* background is hidden and the delegate spans the full width of the timeline.
|
||||
*/
|
||||
TimelineDelegate {
|
||||
MessageDelegateBase {
|
||||
id: root
|
||||
|
||||
/**
|
||||
@@ -44,20 +44,6 @@ TimelineDelegate {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -68,26 +54,11 @@ TimelineDelegate {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -130,7 +101,7 @@ TimelineDelegate {
|
||||
*
|
||||
* @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.
|
||||
@@ -164,179 +135,83 @@ TimelineDelegate {
|
||||
*/
|
||||
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.timeline: root.ListView.view
|
||||
Message.contentModel: root.contentModel
|
||||
Message.index: root.index
|
||||
Message.maxContentWidth: contentMaxWidth
|
||||
Message.maxContentWidth: maxContentWidth - bubble.leftPadding - bubble.rightPadding
|
||||
|
||||
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 {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
contentItem: Bubble {
|
||||
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 {
|
||||
id: sectionDelegate
|
||||
Layout.fillWidth: true
|
||||
visible: root.showSection
|
||||
labelText: root.section
|
||||
colorSet: NeoChatConfig.compactLayout || root.alwaysFillWidth ? Kirigami.Theme.View : Kirigami.Theme.Window
|
||||
author: root.author
|
||||
showAuthor: root.showAuthor
|
||||
isThreaded: root.isThreaded
|
||||
|
||||
contentModel: root.contentModel
|
||||
|
||||
showHighlight: root.showHighlight
|
||||
|
||||
isPending: root.isPending
|
||||
|
||||
onSelectedTextChanged: selectedText => {
|
||||
root.Message.selectedText = selectedText;
|
||||
}
|
||||
QQC2.ItemDelegate {
|
||||
id: mainContainer
|
||||
|
||||
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
|
||||
onHoveredLinkChanged: hoveredLink => {
|
||||
root.Message.hoveredLink = hoveredLink;
|
||||
}
|
||||
|
||||
LibNeoChat.DelegateSizeHelper {
|
||||
id: bubbleSizeHelper
|
||||
parentItem: mainContainer
|
||||
leftPadding: avatar.anchors.leftMargin + (NeoChatConfig.showAvatarInTimeline ? avatar.width + bubble.anchors.leftMargin : 0)
|
||||
startBreakpoint: Kirigami.Units.gridUnit * 25
|
||||
endBreakpoint: Kirigami.Units.gridUnit * 40
|
||||
startPercentWidth: root.alwaysFillWidth ? 100 : 90
|
||||
endPercentWidth: root.alwaysFillWidth ? 100 : 60
|
||||
showBackground: root.cardBackground && !NeoChatConfig.compactLayout
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: _private.showMessageMenu()
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedDevices: PointerDevice.TouchScreen
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: _private.showMessageMenu()
|
||||
}
|
||||
}
|
||||
|
||||
function isVisibleInTimeline() {
|
||||
let yoff = Math.round(y - ListView.view.contentY);
|
||||
return (yoff + height > 0 && yoff < ListView.view.height);
|
||||
avatarComponent: KirigamiComponents.AvatarButton {
|
||||
id: avatar
|
||||
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() {
|
||||
@@ -348,11 +223,6 @@ TimelineDelegate {
|
||||
QtObject {
|
||||
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() {
|
||||
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.formcard as FormCard
|
||||
|
||||
import Quotient
|
||||
|
||||
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 : ""
|
||||
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);
|
||||
connect(&m_sizeHelper, &DelegateSizeHelper::leftPaddingChanged, this, [this]() {
|
||||
Q_EMIT leftPaddingChanged();
|
||||
resizeContent();
|
||||
updatePolish();
|
||||
Q_EMIT timelineWidthChanged();
|
||||
markAsDirty();
|
||||
});
|
||||
connect(&m_sizeHelper, &DelegateSizeHelper::rightPaddingChanged, this, [this]() {
|
||||
Q_EMIT rightPaddingChanged();
|
||||
resizeContent();
|
||||
updatePolish();
|
||||
Q_EMIT timelineWidthChanged();
|
||||
markAsDirty();
|
||||
});
|
||||
connect(&m_sizeHelper, &DelegateSizeHelper::availableWidthChanged, this, [this]() {
|
||||
Q_EMIT timelineWidthChanged();
|
||||
markAsDirty();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,7 +35,7 @@ void TimelineDelegate::setContentItem(QQuickItem *item)
|
||||
}
|
||||
|
||||
if (m_contentItem) {
|
||||
disconnect(m_contentItem, &QQuickItem::implicitHeightChanged, this, &TimelineDelegate::updateImplicitHeight);
|
||||
m_contentItem->disconnect(this);
|
||||
m_contentItem->setParentItem(nullptr);
|
||||
}
|
||||
|
||||
@@ -39,13 +43,13 @@ void TimelineDelegate::setContentItem(QQuickItem *item)
|
||||
|
||||
if (m_contentItem) {
|
||||
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();
|
||||
|
||||
updateImplicitHeight();
|
||||
resizeContent();
|
||||
}
|
||||
|
||||
bool TimelineDelegate::alwaysFillWidth()
|
||||
@@ -59,10 +63,16 @@ void TimelineDelegate::setAlwaysFillWidth(bool alwaysFillWidth)
|
||||
return;
|
||||
}
|
||||
m_alwaysFillWidth = alwaysFillWidth;
|
||||
|
||||
if (m_alwaysFillWidth) {
|
||||
m_sizeHelper.setEndPercentWidth(100);
|
||||
} else {
|
||||
m_sizeHelper.setEndPercentWidth(85);
|
||||
}
|
||||
|
||||
Q_EMIT alwaysFillWidthChanged();
|
||||
|
||||
resizeContent();
|
||||
updatePolish();
|
||||
markAsDirty();
|
||||
}
|
||||
|
||||
qreal TimelineDelegate::leftPadding()
|
||||
@@ -85,14 +95,18 @@ void TimelineDelegate::setRightPadding(qreal rightPadding)
|
||||
m_sizeHelper.setRightPadding(rightPadding);
|
||||
}
|
||||
|
||||
qreal TimelineDelegate::timelineWidth()
|
||||
{
|
||||
return m_sizeHelper.availableWidth();
|
||||
}
|
||||
|
||||
void TimelineDelegate::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
{
|
||||
if (newGeometry == oldGeometry) {
|
||||
return;
|
||||
if (newGeometry != oldGeometry) {
|
||||
markAsDirty();
|
||||
}
|
||||
|
||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
resizeContent();
|
||||
}
|
||||
|
||||
void TimelineDelegate::componentComplete()
|
||||
@@ -103,8 +117,14 @@ void TimelineDelegate::componentComplete()
|
||||
Q_ASSERT(engine);
|
||||
m_units = engine->singletonInstance<Kirigami::Platform::Units *>("org.kde.kirigami.platform", "Units");
|
||||
Q_ASSERT(m_units);
|
||||
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()
|
||||
@@ -115,21 +135,38 @@ void TimelineDelegate::setCurveValues()
|
||||
m_sizeHelper.setStartBreakpoint(qreal(m_units->gridUnit() * 46));
|
||||
m_sizeHelper.setEndBreakpoint(qreal(m_units->gridUnit() * 66));
|
||||
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()
|
||||
{
|
||||
if (m_contentItem == nullptr || !isComponentComplete()) {
|
||||
if (m_contentItem == nullptr || !isComponentComplete() || m_resizingContent) {
|
||||
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;
|
||||
m_contentItem->setPosition(QPointF(leftPadding, 0));
|
||||
m_contentItem->setSize(QSizeF(availableWidth, m_contentItem->implicitHeight()));
|
||||
updateImplicitHeight();
|
||||
|
||||
m_contentItem->setPosition(QPointF(m_sizeHelper.leftX(), 0));
|
||||
m_contentItem->setSize(QSizeF(m_sizeHelper.availableWidth(), m_contentItem->implicitHeight()));
|
||||
|
||||
m_resizingContent = false;
|
||||
}
|
||||
|
||||
void TimelineDelegate::updateImplicitHeight()
|
||||
|
||||
@@ -50,6 +50,11 @@ class TimelineDelegate : public QQuickItem
|
||||
*/
|
||||
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:
|
||||
TimelineDelegate(QQuickItem *parent = nullptr);
|
||||
|
||||
@@ -65,28 +70,34 @@ public:
|
||||
[[nodiscard]] qreal rightPadding();
|
||||
void setRightPadding(qreal rightPadding);
|
||||
|
||||
[[nodiscard]] qreal timelineWidth();
|
||||
|
||||
Q_SIGNALS:
|
||||
void contentItemChanged();
|
||||
void alwaysFillWidthChanged();
|
||||
void leftPaddingChanged();
|
||||
void rightPaddingChanged();
|
||||
void timelineWidthChanged();
|
||||
|
||||
protected:
|
||||
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
void componentComplete() override;
|
||||
|
||||
private:
|
||||
Kirigami::Platform::Units *m_units = nullptr;
|
||||
void setCurveValues();
|
||||
virtual void setCurveValues();
|
||||
|
||||
DelegateSizeHelper m_sizeHelper;
|
||||
|
||||
bool m_alwaysFillWidth = false;
|
||||
|
||||
void resizeContent();
|
||||
void updateImplicitHeight();
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user