diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 3cdfda22..3ffe7d9a 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -52,8 +52,8 @@ Item { //onModelChanged: if (room) room.sendReset() //reuseItems: true boundsBehavior: Flickable.StopAtBounds - displayMarginBeginning: height / 2 - displayMarginEnd: height / 2 + displayMarginBeginning: height / 4 + displayMarginEnd: height / 4 model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room //pixelAligned: true spacing: 2 diff --git a/resources/qml/TimelineDefaultMessageStyle.qml b/resources/qml/TimelineDefaultMessageStyle.qml index 1d12daef..0866fab6 100644 --- a/resources/qml/TimelineDefaultMessageStyle.qml +++ b/resources/qml/TimelineDefaultMessageStyle.qml @@ -18,7 +18,7 @@ TimelineEvent { id: wrapper ListView.delayRemove: true width: chat.delegateMaxWidth - height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10) + height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 20) anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter //room: chatRoot.roommodel @@ -51,6 +51,8 @@ TimelineEvent { property alias hovered: messageHover.hovered property bool scrolledToThis: false + maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width + data: [ Loader { id: section @@ -131,7 +133,7 @@ TimelineEvent { anchors.top: gridContainer.top anchors.left: gridContainer.left anchors.topMargin: -2 - anchors.leftMargin: wrapper.avatarMargin + 2 + anchors.leftMargin: -2 color: "transparent" border.color: Nheko.theme.red border.width: wrapper.notificationlevel == MtxEvent.Highlight ? 1 : 0 @@ -139,11 +141,13 @@ TimelineEvent { height: contentColumn.implicitHeight + 4 width: contentColumn.implicitWidth + 4 }, - RowLayout { + Row { id: gridContainer - width: wrapper.width + width: wrapper.width - wrapper.avatarMargin + x: wrapper.avatarMargin y: section.visible && section.active ? section.y + section.height : 0 + spacing: Nheko.paddingSmall HoverHandler { id: messageHover @@ -154,23 +158,20 @@ TimelineEvent { messageActions.model = wrapper; messageActions.attached = wrapper; messageActions.anchors.bottomMargin = -gridContainer.y + messageActions.anchors.rightMargin = metadata.width } } } } - Item { - Layout.preferredWidth: wrapper.avatarMargin - } - AbstractButton { ToolTip.delay: Nheko.tooltipDelay ToolTip.text: qsTr("Part of a thread") ToolTip.visible: hovered - Layout.fillHeight: true + height: contentColumn.height visible: wrapper.threadId - Layout.preferredWidth: 4 + width: 4 onClicked: wrapper.room.thread = wrapper.threadId @@ -181,17 +182,16 @@ TimelineEvent { color: TimelineManager.userColor(wrapper.threadId, palette.base) } } - ColumnLayout { + Column { id: contentColumn - Layout.fillWidth: true AbstractButton { id: replyRow visible: wrapper.reply - Layout.fillWidth: true - Layout.maximumHeight: timelineView.height / 8 - Layout.preferredWidth: replyRowLay.implicitWidth - Layout.preferredHeight: replyRowLay.implicitHeight + //Layout.fillWidth: true + //Layout.maximumHeight: timelineView.height / 8 + //Layout.preferredWidth: replyRowLay.implicitWidth + //Layout.preferredHeight: replyRowLay.implicitHeight property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) @@ -202,32 +202,33 @@ TimelineEvent { cursorShape: Qt.PointingHandCursor } - contentItem: RowLayout { + contentItem: Row { id: replyRowLay - anchors.fill: parent - + spacing: Nheko.paddingSmall Rectangle { id: replyLine - Layout.fillHeight: true + height: replyCol.height color: replyRow.userColor - Layout.preferredWidth: 4 + width: 4 } - ColumnLayout { + Column { spacing: 0 + id: replyCol + AbstractButton { id: replyUserButton - Layout.fillWidth: true - contentItem: ElidedLabel { + + contentItem: Label { id: userName_ - fullText: wrapper.reply?.userName ?? '' + text: wrapper.reply?.userName ?? '' color: replyRow.userColor textFormat: Text.RichText - width: parent.width - elideWidth: width + width: wrapper.maxWidth + //elideWidth: wrapper.maxWidth } onClicked: wrapper.room.openUserProfile(wrapper.reply?.userId) } @@ -239,7 +240,7 @@ TimelineEvent { } background: Rectangle { - width: replyRow.implicitContentWidth + //width: replyRow.implicitContentWidth color: Qt.tint(palette.base, Qt.hsla(replyRow.userColor.hslHue, 0.5, replyRow.userColor.hslLightness, 0.1)) } @@ -259,19 +260,16 @@ TimelineEvent { ] } - Item { - // spacer to fill width if needed - Layout.fillWidth: true - } - + }, RowLayout { id: metadata property int iconSize: Math.floor(fontMetrics.ascent * scaling) property double scaling: Settings.bubbles ? 0.75 : 1 - Layout.alignment: Qt.AlignTop | Qt.AlignRight - Layout.preferredWidth: implicitWidth + anchors.right: parent.right + y: section.visible && section.active ? section.y + section.height : 0 + spacing: 2 visible: !isStateEvent @@ -339,8 +337,7 @@ TimelineEvent { } } - } - }, + }, Reactions { id: reactionRow diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index 44726a63..cd1bdcd4 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -620,7 +620,6 @@ Item { roleValue: MtxEvent.Member ColumnLayout { - width: parent?.width ?? 100 NoticeMessage { body: formatted diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index 9ef2e6cc..03623924 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -4,7 +4,7 @@ import ".." import QtQuick.Controls -import QtQuick.Layouts +//import QtQuick.Layouts import im.nheko MatrixText { @@ -41,7 +41,10 @@ MatrixText { }" : "") + // TODO(Nico): Figure out how to support mobile " " + formatted.replace(//g, "").replace(/<\/del>/g, "").replace(//g, "").replace(/<\/strike>/g, "") - Layout.maximumHeight: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight + //Layout.maximumHeight: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight + + //EventDelegateChooser.fillWidth: true + clip: !keepFullText selectByMouse: !Settings.mobileMode && !isReply enabled: !Settings.mobileMode && !isReply diff --git a/src/timeline/EventDelegateChooser.cpp b/src/timeline/EventDelegateChooser.cpp index 2218d9ee..0060907d 100644 --- a/src/timeline/EventDelegateChooser.cpp +++ b/src/timeline/EventDelegateChooser.cpp @@ -131,7 +131,7 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) roleToPropIdx.insert(*role, i); roles.emplace_back(*role); - nhlog::ui()->critical("Found prop {}, idx {}, role {}", prop.name(), i, *role); + // nhlog::ui()->critical("Found prop {}, idx {}, role {}", prop.name(), i, *role); } else { nhlog::ui()->critical("Required property {} not found in model!", prop.name()); } @@ -140,14 +140,15 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) nhlog::ui()->debug("Querying data for id {}", currentId.toStdString()); chooser.room_->multiData(currentId, forReply ? chooser.eventId_ : QString(), roles); + Qt::beginPropertyUpdateGroup(); for (const auto &role : roles) { const auto &roleName = roleNames[role.role()]; - nhlog::ui()->critical("Setting role {}, {} to {}", - role.role(), - roleName.toStdString(), - role.data().toString().toStdString()); + // nhlog::ui()->critical("Setting role {}, {} to {}", + // role.role(), + // roleName.toStdString(), + // role.data().toString().toStdString()); - nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[role.role()]).name()); + // nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[role.role()]).name()); mo->property(roleToPropIdx[role.role()]).write(obj, role.data()); if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end()) @@ -156,14 +157,15 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) if (isReplyNeeded) { const auto roleName = QByteArray("isReply"); - nhlog::ui()->critical("Setting role {} to {}", roleName.toStdString(), forReply); + // nhlog::ui()->critical("Setting role {} to {}", roleName.toStdString(), forReply); - nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[-1]).name()); + // nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[-1]).name()); mo->property(roleToPropIdx[-1]).write(obj, forReply); if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end()) QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req); } + Qt::endPropertyUpdateGroup(); // setInitialProperties(rolesToSet); @@ -188,9 +190,12 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) auto mo = obj->metaObject(); chooser.room_->multiData( currentId, forReply ? chooser.eventId_ : QString(), rolesToRequest); + + Qt::beginPropertyUpdateGroup(); for (const auto &role : rolesToRequest) { mo->property(roleToPropIdx[role.role()]).write(obj, role.data()); } + Qt::endPropertyUpdateGroup(); }; if (!forReply) { @@ -257,11 +262,22 @@ EventDelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status sta child->setParentItem(&chooser); QQmlEngine::setObjectOwnership(child, QQmlEngine::ObjectOwnership::JavaScriptOwnership); + + // connect(child, &QQuickItem::parentChanged, child, [child](QQuickItem *) { + // // QTBUG-115687 + // if (child->flags().testFlag(QQuickItem::ItemObservesViewport)) { + // nhlog::ui()->critical("SETTING OBSERVES VIEWPORT"); + // // Re-trigger the parent traversal to get subtreeTransformChangedEnabled turned + // on child->setFlag(QQuickItem::ItemObservesViewport); + // } + // }); + if (forReply) emit chooser.replyChanged(); else emit chooser.mainChanged(); + chooser.polish(); } else if (status == QQmlIncubator::Error) { auto errors_ = errors(); for (const auto &e : qAsConst(errors_)) @@ -269,3 +285,49 @@ EventDelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status sta } } +void +EventDelegateChooser::updatePolish() +{ + auto mainChild = qobject_cast(eventIncubator.object()); + auto replyChild = qobject_cast(replyIncubator.object()); + + nhlog::ui()->critical("POLISHING {}", (void *)this); + + if (mainChild) { + auto attached = qobject_cast( + qmlAttachedPropertiesObject(mainChild)); + Q_ASSERT(attached != nullptr); + + // in theory we could also reset the width, but that doesn't seem to work nicely for text + // areas because of how they cache it. + mainChild->setWidth(maxWidth_); + mainChild->ensurePolished(); + auto width = mainChild->implicitWidth(); + + if (width > maxWidth_ || attached->fillWidth()) + width = maxWidth_; + + nhlog::ui()->debug( + "Made event delegate width: {}, {}", width, mainChild->metaObject()->className()); + mainChild->setWidth(width); + mainChild->ensurePolished(); + } + + if (replyChild) { + auto attached = qobject_cast( + qmlAttachedPropertiesObject(replyChild)); + Q_ASSERT(attached != nullptr); + + // in theory we could also reset the width, but that doesn't seem to work nicely for text + // areas because of how they cache it. + replyChild->setWidth(maxWidth_); + replyChild->ensurePolished(); + auto width = replyChild->implicitWidth(); + + if (width > maxWidth_ || attached->fillWidth()) + width = maxWidth_; + + replyChild->setWidth(width); + replyChild->ensurePolished(); + } +} diff --git a/src/timeline/EventDelegateChooser.h b/src/timeline/EventDelegateChooser.h index 1cd2d65a..ff67ccd8 100644 --- a/src/timeline/EventDelegateChooser.h +++ b/src/timeline/EventDelegateChooser.h @@ -2,9 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -// A DelegateChooser like the one, that was added to Qt5.12 (in labs), but compatible with older Qt -// versions see KDE/kquickitemviews see qtdeclarative/qqmldelagatecomponent - #pragma once #include @@ -17,6 +14,32 @@ #include "TimelineModel.h" +class EventDelegateChooserAttachedType : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool fillWidth READ fillWidth WRITE setFillWidth NOTIFY fillWidthChanged) + QML_ANONYMOUS +public: + EventDelegateChooserAttachedType(QObject *parent) + : QObject(parent) + { + } + bool fillWidth() const { return fillWidth_; } + void setFillWidth(bool fill) + { + fillWidth_ = fill; + emit fillWidthChanged(); + } +signals: + void fillWidthChanged(); + +private: + bool fillWidth_ = false, keepAspectRatio = false; + double aspectRatio = 1.; + int maxWidth = -1; + int maxHeight = -1; +}; + class EventDelegateChoice : public QObject { Q_OBJECT @@ -51,14 +74,18 @@ class EventDelegateChooser : public QQuickItem QML_ELEMENT Q_CLASSINFO("DefaultProperty", "choices") -public: + QML_ATTACHED(EventDelegateChooserAttachedType) + Q_PROPERTY(QQmlListProperty choices READ choices CONSTANT FINAL) Q_PROPERTY(QQuickItem *main READ main NOTIFY mainChanged FINAL) Q_PROPERTY(QQuickItem *reply READ reply NOTIFY replyChanged FINAL) Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged REQUIRED FINAL) Q_PROPERTY(QString replyTo READ replyTo WRITE setReplyTo NOTIFY replyToChanged REQUIRED FINAL) Q_PROPERTY(TimelineModel *room READ room WRITE setRoom NOTIFY roomChanged REQUIRED FINAL) + Q_PROPERTY(bool sameWidth READ sameWidth WRITE setSameWidth NOTIFY sameWidthChanged) + Q_PROPERTY(int maxWidth READ maxWidth WRITE setMaxWidth NOTIFY maxWidthChanged) +public: QQmlListProperty choices(); [[nodiscard]] QQuickItem *main() const @@ -70,6 +97,20 @@ public: return qobject_cast(replyIncubator.object()); } + bool sameWidth() const { return sameWidth_; } + void setSameWidth(bool width) + { + sameWidth_ = width; + emit sameWidthChanged(); + } + bool maxWidth() const { return maxWidth_; } + void setMaxWidth(int width) + { + maxWidth_ = width; + emit maxWidthChanged(); + polish(); + } + void setRoom(TimelineModel *m) { if (m != room_) { @@ -105,12 +146,21 @@ public: void componentComplete() override; + static EventDelegateChooserAttachedType *qmlAttachedProperties(QObject *object) + { + return new EventDelegateChooserAttachedType(object); + } + + void updatePolish() override; + signals: void mainChanged(); void replyChanged(); void roomChanged(); void eventIdChanged(); void replyToChanged(); + void sameWidthChanged(); + void maxWidthChanged(); private: struct DelegateIncubator final : public QQmlIncubator @@ -142,6 +192,8 @@ private: TimelineModel *room_{nullptr}; QString eventId_; QString replyId; + bool sameWidth_ = false; + int maxWidth_ = 400; static void appendChoice(QQmlListProperty *, EventDelegateChoice *); static qsizetype choiceCount(QQmlListProperty *); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 3e0c6688..f5b9e142 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -601,12 +601,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r case UserName: return QVariant(displayName(QString::fromStdString(acc::sender(event)))); case UserPowerlevel: { - return static_cast(mtx::events::state::PowerLevels{ - cache::client() - ->getStateEvent(room_id_.toStdString()) - .value_or(mtx::events::StateEvent{}) - .content} - .user_level(acc::sender(event))); + return static_cast( + permissions_.powerlevelEvent().user_level(acc::sender(event))); } case Day: {