diff --git a/src/app/neochatconfig.kcfg b/src/app/neochatconfig.kcfg index 54dee5269..6a5b53420 100644 --- a/src/app/neochatconfig.kcfg +++ b/src/app/neochatconfig.kcfg @@ -207,10 +207,6 @@ - - - false - false diff --git a/src/app/roommanager.cpp b/src/app/roommanager.cpp index 5166ba1d7..7baa01dce 100644 --- a/src/app/roommanager.cpp +++ b/src/app/roommanager.cpp @@ -129,15 +129,6 @@ RoomManager::RoomManager(QObject *parent) m_messageFilterModel->invalidate(); } }); - ContentProvider::self().setThreadsEnabled(NeoChatConfig::threads()); - MessageModel::setThreadsEnabled(NeoChatConfig::threads()); - connect(NeoChatConfig::self(), &NeoChatConfig::ThreadsChanged, this, [this] { - ContentProvider::self().setThreadsEnabled(NeoChatConfig::threads()); - MessageModel::setThreadsEnabled(NeoChatConfig::threads()); - if (m_timelineModel) { - Q_EMIT m_timelineModel->threadsEnabledChanged(); - } - }); connect(NeoChatConfig::self(), &NeoChatConfig::SortOrderChanged, this, [this]() { m_sortFilterRoomTreeModel->invalidate(); }); diff --git a/src/chatbar/SendBar.qml b/src/chatbar/SendBar.qml index 5d9234556..9b3e5d19b 100644 --- a/src/chatbar/SendBar.qml +++ b/src/chatbar/SendBar.qml @@ -99,7 +99,6 @@ RowLayout { visible: root.maxAvailableWidth < root.overflowWidth && (root.contentModel?.type ?? true) === LibNeoChat.ChatBarType.Room icon.name: "overflow-menu" - enabled: root.chatButtonHelper.richFormatEnabled text: i18nc("@action:button", "Format Text") display: QQC2.AbstractButton.IconOnly checkable: true @@ -153,7 +152,9 @@ RowLayout { } QQC2.ToolButton { id: attachmentButton - visible: !root.contentModel.hasAttachment && (root.contentModel?.type ?? true) === LibNeoChat.ChatBarType.Room && root.maxAvailableWidth >= root.overflowWidth + visible: !root.contentModel.hasAttachment && + ((root.contentModel?.type ?? true) === LibNeoChat.ChatBarType.Room || (root.contentModel?.type ?? true) === LibNeoChat.ChatBarType.Thread) && + root.maxAvailableWidth >= root.overflowWidth icon.name: "mail-attachment" text: i18nc("@action:button", "Attach an image or file") display: QQC2.AbstractButton.IconOnly diff --git a/src/devtools/FeatureFlagPage.qml b/src/devtools/FeatureFlagPage.qml index 5c9e5b62a..b76587fd1 100644 --- a/src/devtools/FeatureFlagPage.qml +++ b/src/devtools/FeatureFlagPage.qml @@ -14,13 +14,6 @@ FormCard.FormCard { Layout.topMargin: Kirigami.Units.largeSpacing - FormCard.FormCheckDelegate { - id: roomAccountDataVisibleCheck - text: i18nc("@option:check Enable the matrix 'threads' feature", "Threads") - checked: NeoChatConfig.threads - - onToggled: NeoChatConfig.threads = checked - } FormCard.FormCheckDelegate { text: i18nc("@option:check Enable the matrix feature to add a phone number as a third party ID", "Add phone numbers as 3PIDs") checked: NeoChatConfig.phone3PId diff --git a/src/messagecontent/CMakeLists.txt b/src/messagecontent/CMakeLists.txt index c747801aa..f369524ca 100644 --- a/src/messagecontent/CMakeLists.txt +++ b/src/messagecontent/CMakeLists.txt @@ -9,6 +9,7 @@ ecm_add_qml_module(MessageContent GENERATE_PLUGIN_SOURCE BaseMessageComponentChooser.qml MessageComponentChooser.qml ReplyMessageComponentChooser.qml + ThreadBodyMessageComponentChooser.qml AuthorComponent.qml AudioComponent.qml CodeComponent.qml diff --git a/src/messagecontent/MessageComponentChooser.qml b/src/messagecontent/MessageComponentChooser.qml index 7d5d2c1e3..d30203ef5 100644 --- a/src/messagecontent/MessageComponentChooser.qml +++ b/src/messagecontent/MessageComponentChooser.qml @@ -29,10 +29,16 @@ BaseMessageComponentChooser { DelegateChoice { roleValue: MessageComponentType.ChatBar delegate: ChatBarCore { + + /** + * @brief The ChatBarCache to use. + */ + required property ChatBarCache chatBarCache + Layout.fillWidth: true Layout.maximumWidth: Message.maxContentWidth room: Message.room - chatBarType: LibNeoChat.ChatBarType.Edit + chatBarType: chatBarCache.isEditing ? LibNeoChat.ChatBarType.Edit : LibNeoChat.ChatBarType.Thread maxAvailableWidth: Message.maxContentWidth } } diff --git a/src/messagecontent/ThreadBodyComponent.qml b/src/messagecontent/ThreadBodyComponent.qml index 220af9f75..f498b073e 100644 --- a/src/messagecontent/ThreadBodyComponent.qml +++ b/src/messagecontent/ThreadBodyComponent.qml @@ -40,7 +40,7 @@ ColumnLayout { id: threadRepeater model: root.Message.contentModel.modelForThread(root.threadRoot); - delegate: BaseMessageComponentChooser { + delegate: ThreadBodyMessageComponentChooser { onSelectedTextChanged: selectedText => { root.selectedTextChanged(selectedText); } diff --git a/src/messagecontent/ThreadBodyMessageComponentChooser.qml b/src/messagecontent/ThreadBodyMessageComponentChooser.qml new file mode 100644 index 000000000..992f46ca8 --- /dev/null +++ b/src/messagecontent/ThreadBodyMessageComponentChooser.qml @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2026 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +import QtQuick +import QtQuick.Layouts +import Qt.labs.qmlmodels + +import org.kde.neochat +import org.kde.neochat.libneochat as LibNeoChat + +/** + * @brief Select a message component based on a MessageComponentType. + */ +BaseMessageComponentChooser { + id: root + + DelegateChoice { + roleValue: MessageComponentType.ChatBar + delegate: ChatBarCore { + Layout.fillWidth: true + Layout.maximumWidth: Message.maxContentWidth + room: Message.room + chatBarType: LibNeoChat.ChatBarType.Thread + maxAvailableWidth: Message.maxContentWidth + } + } +} diff --git a/src/messagecontent/contentprovider.cpp b/src/messagecontent/contentprovider.cpp index 71631cced..921913838 100644 --- a/src/messagecontent/contentprovider.cpp +++ b/src/messagecontent/contentprovider.cpp @@ -113,13 +113,4 @@ PollHandler *ContentProvider::handlerForPoll(NeoChatRoom *room, const QString &e return m_pollHandlers.object(eventId); } -void ContentProvider::setThreadsEnabled(bool enableThreads) -{ - EventMessageContentModel::setThreadsEnabled(enableThreads); - - for (const auto &key : m_eventContentModels.keys()) { - m_eventContentModels.object(key)->threadsEnabledChanged(); - } -} - #include "moc_contentprovider.cpp" diff --git a/src/messagecontent/contentprovider.h b/src/messagecontent/contentprovider.h index 012641979..acc02e95d 100644 --- a/src/messagecontent/contentprovider.h +++ b/src/messagecontent/contentprovider.h @@ -81,8 +81,6 @@ public: */ Q_INVOKABLE PollHandler *handlerForPoll(NeoChatRoom *room, const QString &eventId); - void setThreadsEnabled(bool enableThreads); - private: explicit ContentProvider(QObject *parent = nullptr); diff --git a/src/messagecontent/models/eventmessagecontentmodel.cpp b/src/messagecontent/models/eventmessagecontentmodel.cpp index 8b0e09ef4..b20440b63 100644 --- a/src/messagecontent/models/eventmessagecontentmodel.cpp +++ b/src/messagecontent/models/eventmessagecontentmodel.cpp @@ -22,8 +22,6 @@ using namespace Quotient; -bool EventMessageContentModel::m_threadsEnabled = false; - EventMessageContentModel::EventMessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply, bool isPending, MessageContentModel *parent) : MessageContentModel(room, eventId, parent) , m_currentState(isPending ? Pending : Unknown) @@ -104,9 +102,6 @@ void EventMessageContentModel::initializeModel() } } }); - connect(this, &EventMessageContentModel::threadsEnabledChanged, this, [this]() { - resetModel(); - }); connect(m_room, &Room::updatedEvent, this, [this](const QString &eventId) { if (eventId == m_eventId) { updateReactionModel(); @@ -304,7 +299,7 @@ QList EventMessageContentModel::messageContentComponents(bool } const auto roomMessageEvent = eventCast(event.first); - if (m_threadsEnabled && roomMessageEvent + if (roomMessageEvent && ((roomMessageEvent->isThreaded() && roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) || m_room->threads().contains(roomMessageEvent->id()))) { newComponents += MessageComponent{MessageComponentType::Separator, {}, {}}; @@ -328,14 +323,14 @@ std::optional EventMessageContentModel::getReplyEventId() if (roomMessageEvent == nullptr) { return std::nullopt; } - if (!roomMessageEvent->isReply(!m_threadsEnabled)) { + if (!roomMessageEvent->isReply()) { if (m_replyModel) { m_replyModel->disconnect(this); m_replyModel->deleteLater(); } return std::nullopt; } - return roomMessageEvent->isReply(!m_threadsEnabled) ? std::make_optional(roomMessageEvent->replyEventId(!m_threadsEnabled)) : std::nullopt; + return roomMessageEvent->isReply() ? std::make_optional(roomMessageEvent->replyEventId()) : std::nullopt; } QList EventMessageContentModel::componentsForType(MessageComponentType::Type type) @@ -475,9 +470,4 @@ ThreadModel *EventMessageContentModel::modelForThread(const QString &threadRootI return ContentProvider::self().modelForThread(m_room, threadRootId); } -void EventMessageContentModel::setThreadsEnabled(bool enableThreads) -{ - m_threadsEnabled = enableThreads; -} - #include "moc_eventmessagecontentmodel.cpp" diff --git a/src/messagecontent/models/eventmessagecontentmodel.h b/src/messagecontent/models/eventmessagecontentmodel.h index 38f740d9a..026a83621 100644 --- a/src/messagecontent/models/eventmessagecontentmodel.h +++ b/src/messagecontent/models/eventmessagecontentmodel.h @@ -43,11 +43,8 @@ public: */ Q_INVOKABLE ThreadModel *modelForThread(const QString &threadRootId); - static void setThreadsEnabled(bool enableThreads); - Q_SIGNALS: void eventUpdated(); - void threadsEnabledChanged(); private: void initializeModel(); @@ -74,6 +71,4 @@ private: void updateItineraryModel(); void updateReactionModel(); - - static bool m_threadsEnabled; }; diff --git a/src/timeline/DelegateContextMenu.qml b/src/timeline/DelegateContextMenu.qml index 2c0f3349c..5b7e2d57a 100644 --- a/src/timeline/DelegateContextMenu.qml +++ b/src/timeline/DelegateContextMenu.qml @@ -181,7 +181,7 @@ KirigamiComponents.ConvergentContextMenu { Kirigami.Action { id: replyThreadAction - visible: (root.messageComponentType !== MessageComponentType.Other || NeoChatConfig.relateAnyEvent) && NeoChatConfig.threads + visible: (root.messageComponentType !== MessageComponentType.Other || NeoChatConfig.relateAnyEvent) text: i18nc("@action:inmenu", "Reply in Thread") icon.name: "dialog-messages" onTriggered: { diff --git a/src/timeline/messagedelegate.cpp b/src/timeline/messagedelegate.cpp index 5f7b9b029..d9bbba256 100644 --- a/src/timeline/messagedelegate.cpp +++ b/src/timeline/messagedelegate.cpp @@ -87,7 +87,6 @@ void MessageDelegateBase::setIsThreaded(bool isThreaded) return; } m_isThreaded = isThreaded; - setAlwaysFillWidth(m_isThreaded || m_compactMode); setPercentageValues(m_isThreaded || m_compactMode); updateAvatar(); Q_EMIT isThreadedChanged(); @@ -138,11 +137,8 @@ void MessageDelegateBase::setPercentageValues(bool fillWidth) void MessageDelegateBase::setContentPadding() { - qreal selectionOffset = (m_showSelection && m_selectionItem) ? m_selectionItem->implicitWidth() + (m_spacing * 2) : 0; - qreal avatarOffset = (leaveAvatarSpace() ? m_avatarSize + m_spacing : 0); - - m_contentSizeHelper.setLeftPadding(m_sizeHelper.leftX() + selectionOffset + avatarOffset); - m_contentSizeHelper.setRightPadding(m_sizeHelper.rightPadding()); + m_contentSizeHelper.setLeftPadding(m_sizeHelper.leftX()); + m_contentSizeHelper.setRightPadding(m_compactMode ? m_sizeHelper.rightPadding() : 0); } qreal MessageDelegateBase::maxContentWidth() const @@ -229,7 +225,7 @@ bool MessageDelegateBase::leaveAvatarSpace() const bool MessageDelegateBase::showAvatar() const { - return m_enableAvatars && m_showAuthor && !showMessageOnRight(); + return m_enableAvatars && (m_showAuthor || m_isThreaded) && !showMessageOnRight(); } void MessageDelegateBase::updateAvatar() @@ -434,7 +430,7 @@ void MessageDelegateBase::setCompactMode(bool compactMode) return; } m_compactMode = compactMode; - setAlwaysFillWidth(m_isThreaded || m_compactMode); + setAlwaysFillWidth(m_compactMode); setPercentageValues(m_isThreaded || m_compactMode); setBaseRightPadding(); @@ -669,7 +665,7 @@ void MessageDelegateBase::updateImplicitHeight() bool MessageDelegateBase::showMessageOnRight() const { - return m_showLocalMessagesOnRight && !m_alwaysFillWidth && m_author && m_author->isLocalMember(); + return m_showLocalMessagesOnRight && !m_compactMode && !m_isThreaded && m_author && m_author->isLocalMember(); } void MessageDelegateBase::resizeContent() @@ -697,22 +693,25 @@ void MessageDelegateBase::resizeContent() nextY += m_sectionItem->implicitHeight() + m_spacing; } qreal yAdd = 0.0; + qreal nextX = m_sizeHelper.leftX(); if (m_showSelection && m_selectionItem) { - m_selectionItem->setPosition(QPointF(m_sizeHelper.leftX(), nextY)); + m_selectionItem->setPosition(QPointF(nextX, nextY)); m_selectionItem->setSize(QSizeF(m_selectionItem->implicitWidth(), m_selectionItem->implicitHeight())); yAdd = m_selectionItem->implicitHeight(); + nextX += m_selectionItem->implicitWidth() + m_spacing; } if (showAvatar() && m_avatarItem) { - m_avatarItem->setPosition( - QPointF(m_showSelection && m_selectionItem ? m_sizeHelper.leftX() + m_selectionItem->implicitWidth() + (m_spacing * 2) : m_sizeHelper.leftX(), - nextY)); + m_avatarItem->setPosition(QPointF(nextX, nextY)); m_avatarItem->setSize(QSizeF(m_avatarItem->implicitWidth(), m_avatarItem->implicitHeight())); yAdd = std::max(yAdd, m_avatarItem->implicitHeight()); + nextX += m_avatarItem->implicitWidth() + m_spacing; + } else if (leaveAvatarSpace()) { + nextX += m_avatarSize + m_spacing; } 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(); + const auto contentItemWidth = m_compactMode || m_isThreaded ? m_contentSizeHelper.availableWidth() - nextX + : std::min(m_contentItem->implicitWidth(), m_contentSizeHelper.availableWidth()); + const auto contentX = showMessageOnRight() ? m_sizeHelper.rightX() - contentItemWidth - 1 : nextX; m_contentItem->setPosition(QPointF(contentX, nextY)); m_contentItem->setSize(QSizeF(contentItemWidth, m_contentItem->implicitHeight())); yAdd = std::max(yAdd, m_contentItem->implicitHeight()); diff --git a/src/timeline/models/messagemodel.cpp b/src/timeline/models/messagemodel.cpp index 24ab365d0..be0c29907 100644 --- a/src/timeline/models/messagemodel.cpp +++ b/src/timeline/models/messagemodel.cpp @@ -27,7 +27,6 @@ using namespace Quotient; std::function MessageModel::m_hiddenFilter = [](const Quotient::RoomEvent *) -> bool { return false; }; -bool MessageModel::m_threadsEnabled = false; MessageModel::MessageModel(QObject *parent) : QAbstractListModel(parent) @@ -40,10 +39,6 @@ MessageModel::MessageModel(QObject *parent) connect(this, &MessageModel::modelReset, this, [this]() { m_resetting = false; }); - - connect(this, &MessageModel::threadsEnabledChanged, this, [this]() { - Q_EMIT dataChanged(index(0), index(rowCount() - 1), {DelegateTypeRole, ContentModelRole, IsThreadedRole, SpecialMarksRole}); - }); } NeoChatRoom *MessageModel::room() const @@ -151,7 +146,7 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const } auto roomMessageEvent = eventCast(&event.value().get()); - if (m_threadsEnabled && roomMessageEvent && roomMessageEvent->isThreaded()) { + if (roomMessageEvent && roomMessageEvent->isThreaded()) { return QVariant::fromValue( ContentProvider::self().contentModelForEvent(eventRoom, roomMessageEvent->threadRootEventId())); } @@ -200,7 +195,7 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const } auto roomMessageEvent = eventCast(&event.value().get()); - if (m_threadsEnabled && roomMessageEvent && (roomMessageEvent->isThreaded() || eventRoom->threads().contains(event.value().get().id()))) { + if (roomMessageEvent && (roomMessageEvent->isThreaded() || eventRoom->threads().contains(event.value().get().id()))) { const auto &thread = eventRoom->threads().value(roomMessageEvent->isThreaded() ? roomMessageEvent->threadRootEventId() : event.value().get().id()); if (thread.latestEventId != event.value().get().id()) { return EventStatus::Hidden; @@ -231,9 +226,6 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const } if (role == IsThreadedRole) { - if (!m_threadsEnabled) { - return false; - } if (auto roomMessageEvent = eventCast(&event.value().get())) { return roomMessageEvent->isThreaded(); } @@ -559,9 +551,4 @@ void MessageModel::setHiddenFilter(std::function hiddenFilter); - static void setThreadsEnabled(bool enableThreads); - Q_SIGNALS: /** * @brief Emitted when the room is changed. @@ -170,8 +168,6 @@ Q_SIGNALS: */ void newLocalUserEventAdded(); - void threadsEnabledChanged(); - protected: QPointer m_room; QPersistentModelIndex m_lastReadEventIndex; @@ -200,5 +196,4 @@ private: void createEventObjects(const Quotient::RoomEvent *event); static std::function m_hiddenFilter; - static bool m_threadsEnabled; }; diff --git a/src/timeline/models/timelinemodel.cpp b/src/timeline/models/timelinemodel.cpp index 5b6ffeaff..495dd20a2 100644 --- a/src/timeline/models/timelinemodel.cpp +++ b/src/timeline/models/timelinemodel.cpp @@ -16,8 +16,6 @@ TimelineModel::TimelineModel(QObject *parent) addSourceModel(m_timelineMessageModel); m_timelineEndModel = new TimelineEndModel(this); addSourceModel(m_timelineEndModel); - - connect(this, &TimelineModel::threadsEnabledChanged, m_timelineMessageModel, &TimelineMessageModel::threadsEnabledChanged); } NeoChatRoom *TimelineModel::room() const diff --git a/src/timeline/models/timelinemodel.h b/src/timeline/models/timelinemodel.h index 3ee6a9dd1..b08f417b3 100644 --- a/src/timeline/models/timelinemodel.h +++ b/src/timeline/models/timelinemodel.h @@ -156,7 +156,6 @@ public: Q_SIGNALS: void roomChanged(); - void threadsEnabledChanged(); private: TimelineMessageModel *m_timelineMessageModel = nullptr;