Add support for copying & deleting multiple messages at once
BUG: 496458
This commit is contained in:
committed by
Joshua Goins
parent
0f634ff795
commit
f5d726989f
@@ -229,6 +229,58 @@ Kirigami.Page {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kirigami.InlineMessage {
|
||||||
|
id: selectedMessagesControl
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
showCloseButton: false
|
||||||
|
visible: root.currentRoom?.selectedMessageCount > 0
|
||||||
|
position: Kirigami.InlineMessage.Position.Header
|
||||||
|
type: Kirigami.MessageType.Positive
|
||||||
|
icon.name: "edit-select-all-symbolic"
|
||||||
|
|
||||||
|
text: i18nc("@info", "Selected Messages: %1", root.currentRoom?.selectedMessageCount)
|
||||||
|
|
||||||
|
actions: [
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18nc("@action:button", "Copy Conversation")
|
||||||
|
icon.name: "edit-copy"
|
||||||
|
onTriggered: {
|
||||||
|
Clipboard.saveText(root.currentRoom.getFormattedSelectedMessages())
|
||||||
|
showPassiveNotification(i18nc("@info", "Conversation copied to clipboard"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18nc("@action:button", "Delete Messages")
|
||||||
|
icon.name: "trash-empty-symbolic"
|
||||||
|
icon.color: Kirigami.Theme.negativeTextColor
|
||||||
|
enabled: root.currentRoom?.canDeleteSelectedMessages
|
||||||
|
onTriggered: {
|
||||||
|
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
|
||||||
|
title: i18nc("@title:dialog", "Remove Messages"),
|
||||||
|
placeholder: i18nc("@info:placeholder", "Optionally give a reason for removing these messages"),
|
||||||
|
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
|
||||||
|
icon: "delete",
|
||||||
|
reporting: false,
|
||||||
|
connection: root.currentRoom.connection,
|
||||||
|
}, {
|
||||||
|
title: i18nc("@title:dialog", "Remove Messages"),
|
||||||
|
width: Kirigami.Units.gridUnit * 25
|
||||||
|
}) as ReasonDialog;
|
||||||
|
dialog.accepted.connect(reason => {
|
||||||
|
root.currentRoom.deleteSelectedMessages(reason);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
icon.name: "dialog-close"
|
||||||
|
icon.color: Kirigami.Theme.negativeTextColor
|
||||||
|
onTriggered: root.currentRoom.clearSelectedMessages()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
Kirigami.InlineMessage {
|
Kirigami.InlineMessage {
|
||||||
id: banner
|
id: banner
|
||||||
|
|
||||||
|
|||||||
@@ -604,6 +604,7 @@ QString RoomManager::findSpaceIdForCurrentRoom() const
|
|||||||
void RoomManager::setCurrentRoom(const QString &roomId)
|
void RoomManager::setCurrentRoom(const QString &roomId)
|
||||||
{
|
{
|
||||||
if (m_currentRoom != nullptr) {
|
if (m_currentRoom != nullptr) {
|
||||||
|
m_currentRoom->clearSelectedMessages();
|
||||||
m_currentRoom->disconnect(this);
|
m_currentRoom->disconnect(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,8 @@
|
|||||||
#include <KJobTrackerInterface>
|
#include <KJobTrackerInterface>
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
std::function<bool(const Quotient::RoomEvent *)> NeoChatRoom::m_hiddenFilter = [](const Quotient::RoomEvent *) -> bool {
|
std::function<bool(const Quotient::RoomEvent *)> NeoChatRoom::m_hiddenFilter = [](const Quotient::RoomEvent *) -> bool {
|
||||||
@@ -630,7 +632,14 @@ bool NeoChatRoom::isUserBanned(const QString &user) const
|
|||||||
|
|
||||||
void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reason)
|
void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reason)
|
||||||
{
|
{
|
||||||
doDeleteMessagesByUser(user, reason);
|
QStringList events;
|
||||||
|
for (const auto &event : messageEvents()) {
|
||||||
|
if (event->senderId() == user && !event->isRedacted() && !event.viewAs<RedactionEvent>() && !event->isStateEvent()) {
|
||||||
|
events += event->id();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doDeleteMessageIds(events, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NeoChatRoom::historyVisibility() const
|
QString NeoChatRoom::historyVisibility() const
|
||||||
@@ -761,16 +770,10 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
|
QCoro::Task<void> NeoChatRoom::doDeleteMessageIds(const QStringList eventIds, QString reason)
|
||||||
{
|
{
|
||||||
QStringList events;
|
for (const auto &eventId : eventIds) {
|
||||||
for (const auto &event : messageEvents()) {
|
auto job = connection()->callApi<RedactEventJob>(id(), eventId, connection()->generateTxnId(), reason);
|
||||||
if (event->senderId() == user && !event->isRedacted() && !event.viewAs<RedactionEvent>() && !event->isStateEvent()) {
|
|
||||||
events += event->id();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const auto &e : events) {
|
|
||||||
auto job = connection()->callApi<RedactEventJob>(id(), QString::fromLatin1(QUrl::toPercentEncoding(e)), connection()->generateTxnId(), reason);
|
|
||||||
co_await qCoro(job.get(), &BaseJob::finished);
|
co_await qCoro(job.get(), &BaseJob::finished);
|
||||||
if (job->error() != BaseJob::Success) {
|
if (job->error() != BaseJob::Success) {
|
||||||
qWarning() << "Error: \"" << job->error() << "\" while deleting messages. Aborting";
|
qWarning() << "Error: \"" << job->error() << "\" while deleting messages. Aborting";
|
||||||
@@ -1963,4 +1966,96 @@ QList<QString> NeoChatRoom::sortedMemberIds() const
|
|||||||
return m_sortedMemberIds;
|
return m_sortedMemberIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::selectedMessageCount() const
|
||||||
|
{
|
||||||
|
return m_selectedMessageIds.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeoChatRoom::canDeleteSelectedMessages() const
|
||||||
|
{
|
||||||
|
if (canSendState("redact"_L1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString localUserId = connection()->userId();
|
||||||
|
return std::ranges::all_of(m_selectedMessageIds, [this, localUserId](const QString &eventId) {
|
||||||
|
const auto eventIt = findInTimeline(eventId);
|
||||||
|
if (eventIt == historyEdge()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoomEvent *event = eventIt->event();
|
||||||
|
return event && (event->senderId() == localUserId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeoChatRoom::isMessageSelected(const QString &eventId) const
|
||||||
|
{
|
||||||
|
return m_selectedMessageIds.contains(eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::toggleMessageSelection(const QString &eventId)
|
||||||
|
{
|
||||||
|
if (!m_selectedMessageIds.remove(eventId)) {
|
||||||
|
m_selectedMessageIds.insert(eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_EMIT selectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NeoChatRoom::getFormattedSelectedMessages() const
|
||||||
|
{
|
||||||
|
QVector<const RoomEvent *> events;
|
||||||
|
events.reserve(m_selectedMessageIds.size());
|
||||||
|
|
||||||
|
std::ranges::copy(m_selectedMessageIds | std::views::transform([this](const QString &eventId) -> const RoomEvent * {
|
||||||
|
const auto eventIt = findInTimeline(eventId);
|
||||||
|
return eventIt != historyEdge() ? eventIt->event() : nullptr;
|
||||||
|
}) | std::views::filter([](const RoomEvent *event) {
|
||||||
|
return event != nullptr;
|
||||||
|
}),
|
||||||
|
std::back_inserter(events));
|
||||||
|
|
||||||
|
std::ranges::sort(events, {}, &RoomEvent::originTimestamp);
|
||||||
|
|
||||||
|
QString formattedContent;
|
||||||
|
formattedContent.reserve(events.size() * 256); // estimate an average of 256 characters per message
|
||||||
|
|
||||||
|
for (const RoomEvent *event : events) {
|
||||||
|
formattedContent += EventHandler::authorDisplayName(this, event);
|
||||||
|
formattedContent += u" — "_s;
|
||||||
|
formattedContent += EventHandler::dateTime(this, event).shortDateTime();
|
||||||
|
formattedContent += u'\n';
|
||||||
|
formattedContent += EventHandler::plainBody(this, event);
|
||||||
|
formattedContent += u"\n\n"_s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedContent.trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::deleteSelectedMessages(const QString &reason)
|
||||||
|
{
|
||||||
|
QStringList events;
|
||||||
|
for (const auto &eventId : m_selectedMessageIds) {
|
||||||
|
const auto eventIt = findInTimeline(eventId);
|
||||||
|
if (eventIt == historyEdge()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoomEvent *event = eventIt->event();
|
||||||
|
if (event && !event->isRedacted() && !is<RedactionEvent>(*event)) {
|
||||||
|
events += eventId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doDeleteMessageIds(events, reason);
|
||||||
|
clearSelectedMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::clearSelectedMessages()
|
||||||
|
{
|
||||||
|
m_selectedMessageIds.clear();
|
||||||
|
Q_EMIT selectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_neochatroom.cpp"
|
#include "moc_neochatroom.cpp"
|
||||||
|
|||||||
@@ -220,6 +220,16 @@ class NeoChatRoom : public Quotient::Room
|
|||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool spaceHasUnreadMessages READ spaceHasUnreadMessages NOTIFY spaceHasUnreadMessagesChanged)
|
Q_PROPERTY(bool spaceHasUnreadMessages READ spaceHasUnreadMessages NOTIFY spaceHasUnreadMessagesChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of selected messages in the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int selectedMessageCount READ selectedMessageCount NOTIFY selectionChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the user can delete the selected messages.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool canDeleteSelectedMessages READ canDeleteSelectedMessages NOTIFY selectionChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
|
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
|
||||||
|
|
||||||
@@ -676,6 +686,41 @@ public:
|
|||||||
*/
|
*/
|
||||||
QList<QString> sortedMemberIds() const;
|
QList<QString> sortedMemberIds() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of selected messages in the room.
|
||||||
|
*/
|
||||||
|
int selectedMessageCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the user can delete the selected messages.
|
||||||
|
*/
|
||||||
|
bool canDeleteSelectedMessages() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the given message is selected.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE bool isMessageSelected(const QString &eventId) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Toggle the selection state of the given message.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void toggleMessageSelection(const QString &eventId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the content of the selected messages formatted as a single string.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE QString getFormattedSelectedMessages() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete the selected messages with an optional reason.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void deleteSelectedMessages(const QString &reason = QString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear the selection of messages.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void clearSelectedMessages();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_visible = false;
|
bool m_visible = false;
|
||||||
|
|
||||||
@@ -693,7 +738,7 @@ private:
|
|||||||
void onAddHistoricalTimelineEvents(rev_iter_t from) override;
|
void onAddHistoricalTimelineEvents(rev_iter_t from) override;
|
||||||
void onRedaction(const Quotient::RoomEvent &prevEvent, const Quotient::RoomEvent &after) override;
|
void onRedaction(const Quotient::RoomEvent &prevEvent, const Quotient::RoomEvent &after) override;
|
||||||
|
|
||||||
QCoro::Task<void> doDeleteMessagesByUser(const QString &user, QString reason);
|
QCoro::Task<void> doDeleteMessageIds(const QStringList eventIds, QString reason);
|
||||||
QCoro::Task<void> doUploadFile(QUrl url, QString body = QString(), std::optional<Quotient::EventRelation> relatesTo = std::nullopt);
|
QCoro::Task<void> doUploadFile(QUrl url, QString body = QString(), std::optional<Quotient::EventRelation> relatesTo = std::nullopt);
|
||||||
|
|
||||||
std::unique_ptr<Quotient::RoomEvent> m_cachedEvent;
|
std::unique_ptr<Quotient::RoomEvent> m_cachedEvent;
|
||||||
@@ -713,6 +758,7 @@ private:
|
|||||||
|
|
||||||
QString m_lastUnreadHighlightId;
|
QString m_lastUnreadHighlightId;
|
||||||
QList<QString> m_sortedMemberIds;
|
QList<QString> m_sortedMemberIds;
|
||||||
|
QSet<QString> m_selectedMessageIds;
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void updatePushNotificationState(QString type);
|
void updatePushNotificationState(QString type);
|
||||||
@@ -752,6 +798,7 @@ Q_SIGNALS:
|
|||||||
void pinnedMessageChanged();
|
void pinnedMessageChanged();
|
||||||
void highlightCycleStartedChanged();
|
void highlightCycleStartedChanged();
|
||||||
void spaceHasUnreadMessagesChanged();
|
void spaceHasUnreadMessagesChanged();
|
||||||
|
void selectionChanged();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Request a message be shown to the user of the given type.
|
* @brief Request a message be shown to the user of the given type.
|
||||||
|
|||||||
@@ -407,6 +407,13 @@ KirigamiComponents.ConvergentContextMenu {
|
|||||||
onTriggered: pinned ? root.room.unpinEvent(root.eventId) : root.room.pinEvent(root.eventId)
|
onTriggered: pinned ? root.room.unpinEvent(root.eventId) : root.room.pinEvent(root.eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kirigami.Action {
|
||||||
|
visible: root.messageComponentType !== MessageComponentType.Other
|
||||||
|
text: root.room.selectedMessageCount > 0 && root.room.isMessageSelected(root.eventId) ? i18nc("@action:inmenu", "Deselect Message") : i18nc("@action:inmenu", "Select Message")
|
||||||
|
icon.name: "edit-select-all-symbolic"
|
||||||
|
onTriggered: root.room.toggleMessageSelection(root.eventId)
|
||||||
|
}
|
||||||
|
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
separator: true
|
separator: true
|
||||||
visible: viewSourceAction.visible
|
visible: viewSourceAction.visible
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
import Qt.labs.qmlmodels
|
import Qt.labs.qmlmodels
|
||||||
@@ -16,6 +18,11 @@ DelegateChooser {
|
|||||||
*/
|
*/
|
||||||
required property NeoChatRoom room
|
required property NeoChatRoom room
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether to show selection controls for message delegate.
|
||||||
|
*/
|
||||||
|
property bool showSelectionControl: false
|
||||||
|
|
||||||
role: "delegateType"
|
role: "delegateType"
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
@@ -25,7 +32,9 @@ DelegateChooser {
|
|||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: DelegateType.Message
|
roleValue: DelegateType.Message
|
||||||
delegate: MessageDelegate {}
|
delegate: MessageDelegate {
|
||||||
|
showSelectionControl: root.showSelectionControl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.components as KirigamiComponents
|
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||||
@@ -109,6 +110,16 @@ MessageDelegateBase {
|
|||||||
*/
|
*/
|
||||||
property bool showHighlight: root.isHighlighted || isTemporaryHighlighted
|
property bool showHighlight: root.isHighlighted || isTemporaryHighlighted
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the message is selected.
|
||||||
|
*/
|
||||||
|
property bool selected: root.room.selectedMessageCount > 0 && room.isMessageSelected(eventId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether to show selection controls for this message.
|
||||||
|
*/
|
||||||
|
property bool showSelectionControl: false
|
||||||
|
|
||||||
Message.room: root.room
|
Message.room: root.room
|
||||||
Message.timeline: root.ListView.view
|
Message.timeline: root.ListView.view
|
||||||
Message.contentModel: root.contentModel
|
Message.contentModel: root.contentModel
|
||||||
@@ -120,6 +131,7 @@ MessageDelegateBase {
|
|||||||
enableAvatars: NeoChatConfig?.showAvatarInTimeline ?? false
|
enableAvatars: NeoChatConfig?.showAvatarInTimeline ?? false
|
||||||
compactMode: NeoChatConfig?.compactLayout ?? false
|
compactMode: NeoChatConfig?.compactLayout ?? false
|
||||||
showLocalMessagesOnRight: NeoChatConfig.showLocalMessagesOnRight
|
showLocalMessagesOnRight: NeoChatConfig.showLocalMessagesOnRight
|
||||||
|
showSelection: root.showSelectionControl && room.selectedMessageCount > 0
|
||||||
|
|
||||||
contentItem: Bubble {
|
contentItem: Bubble {
|
||||||
id: bubble
|
id: bubble
|
||||||
@@ -230,6 +242,20 @@ MessageDelegateBase {
|
|||||||
author: root.author
|
author: root.author
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectionComponent: RowLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
|
||||||
|
QQC2.CheckBox {
|
||||||
|
checked: root.selected
|
||||||
|
onClicked: root.room.toggleMessageSelection(root.eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: _private
|
id: _private
|
||||||
|
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ QQC2.ScrollView {
|
|||||||
model: root.messageFilterModel
|
model: root.messageFilterModel
|
||||||
delegate: EventDelegate {
|
delegate: EventDelegate {
|
||||||
room: _private.room
|
room: _private.room
|
||||||
|
showSelectionControl: true
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
|||||||
@@ -138,7 +138,10 @@ void MessageDelegateBase::setPercentageValues(bool fillWidth)
|
|||||||
|
|
||||||
void MessageDelegateBase::setContentPadding()
|
void MessageDelegateBase::setContentPadding()
|
||||||
{
|
{
|
||||||
m_contentSizeHelper.setLeftPadding(m_sizeHelper.leftX() + (leaveAvatarSpace() ? m_avatarSize + m_spacing : 0));
|
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.setRightPadding(m_sizeHelper.rightPadding());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,6 +542,77 @@ void MessageDelegateBase::updateQuickAction()
|
|||||||
m_quickActionComponent->create(*quickActionIncubator, qmlContext(m_quickActionComponent));
|
m_quickActionComponent->create(*quickActionIncubator, qmlContext(m_quickActionComponent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QQmlComponent *MessageDelegateBase::selectionComponent() const
|
||||||
|
{
|
||||||
|
return m_selectionComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setSelectionComponent(QQmlComponent *selectionComponent)
|
||||||
|
{
|
||||||
|
if (selectionComponent == m_selectionComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_selectionComponent = selectionComponent;
|
||||||
|
Q_EMIT selectionComponentChanged();
|
||||||
|
|
||||||
|
updateSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegateBase::showSelection() const
|
||||||
|
{
|
||||||
|
return m_showSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::setShowSelection(bool showSelection)
|
||||||
|
{
|
||||||
|
if (showSelection == m_showSelection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_showSelection = showSelection;
|
||||||
|
Q_EMIT showSelectionChanged();
|
||||||
|
|
||||||
|
updateSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegateBase::updateSelection()
|
||||||
|
{
|
||||||
|
if (m_selectionComponent && showSelection() && !m_selectionItem && !m_selectionIncubating) {
|
||||||
|
const auto selectionIncubator = new MessageObjectIncubator(
|
||||||
|
m_objectInitialCallback,
|
||||||
|
[this](MessageObjectIncubator *incubator) {
|
||||||
|
if (!incubator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto selectionObject = qobject_cast<QQuickItem *>(incubator->object());
|
||||||
|
if (selectionObject) {
|
||||||
|
// The setting may have changed during the incubation period.
|
||||||
|
if (showSelection()) {
|
||||||
|
m_selectionItem = selectionObject;
|
||||||
|
} else {
|
||||||
|
cleanupItem(selectionObject);
|
||||||
|
}
|
||||||
|
setContentPadding();
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
m_selectionIncubating = false;
|
||||||
|
// We can't cleanup the incubator in the completedCallback otherwise
|
||||||
|
// we use after free when we return to the status changed function
|
||||||
|
// of that incubator
|
||||||
|
QTimer::singleShot(0, this, [this, incubator]() {
|
||||||
|
cleanupIncubator(incubator);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
m_errorCallback);
|
||||||
|
m_activeIncubators.push_back(selectionIncubator);
|
||||||
|
m_selectionComponent->create(*selectionIncubator, qmlContext(m_selectionComponent));
|
||||||
|
m_selectionIncubating = true;
|
||||||
|
} else if (!showSelection() && m_selectionItem) {
|
||||||
|
cleanupItem(m_selectionItem);
|
||||||
|
setContentPadding();
|
||||||
|
markAsDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool MessageDelegateBase::showLocalMessagesOnRight() const
|
bool MessageDelegateBase::showLocalMessagesOnRight() const
|
||||||
{
|
{
|
||||||
return m_showLocalMessagesOnRight;
|
return m_showLocalMessagesOnRight;
|
||||||
@@ -623,10 +697,17 @@ void MessageDelegateBase::resizeContent()
|
|||||||
nextY += m_sectionItem->implicitHeight() + m_spacing;
|
nextY += m_sectionItem->implicitHeight() + m_spacing;
|
||||||
}
|
}
|
||||||
qreal yAdd = 0.0;
|
qreal yAdd = 0.0;
|
||||||
|
if (m_showSelection && m_selectionItem) {
|
||||||
|
m_selectionItem->setPosition(QPointF(m_sizeHelper.leftX(), nextY));
|
||||||
|
m_selectionItem->setSize(QSizeF(m_selectionItem->implicitWidth(), m_selectionItem->implicitHeight()));
|
||||||
|
yAdd = m_selectionItem->implicitHeight();
|
||||||
|
}
|
||||||
if (showAvatar() && m_avatarItem) {
|
if (showAvatar() && m_avatarItem) {
|
||||||
m_avatarItem->setPosition(QPointF(m_sizeHelper.leftX(), nextY));
|
m_avatarItem->setPosition(
|
||||||
|
QPointF(m_showSelection && m_selectionItem ? m_sizeHelper.leftX() + m_selectionItem->implicitWidth() + (m_spacing * 2) : m_sizeHelper.leftX(),
|
||||||
|
nextY));
|
||||||
m_avatarItem->setSize(QSizeF(m_avatarItem->implicitWidth(), m_avatarItem->implicitHeight()));
|
m_avatarItem->setSize(QSizeF(m_avatarItem->implicitWidth(), m_avatarItem->implicitHeight()));
|
||||||
yAdd = m_avatarItem->implicitWidth();
|
yAdd = std::max(yAdd, m_avatarItem->implicitHeight());
|
||||||
}
|
}
|
||||||
if (m_contentItem) {
|
if (m_contentItem) {
|
||||||
const auto contentItemWidth =
|
const auto contentItemWidth =
|
||||||
|
|||||||
@@ -101,6 +101,16 @@ class MessageDelegateBase : public TimelineDelegate
|
|||||||
*/
|
*/
|
||||||
Q_PROPERTY(QQmlComponent *quickActionComponent READ quickActionComponent WRITE setQuickActionComponent NOTIFY quickActionComponentChanged FINAL)
|
Q_PROPERTY(QQmlComponent *quickActionComponent READ quickActionComponent WRITE setQuickActionComponent NOTIFY quickActionComponentChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The component to use to visualize message selection.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QQmlComponent *selectionComponent READ selectionComponent WRITE setSelectionComponent NOTIFY selectionComponentChanged FINAL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether to show the selection component.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool showSelection READ showSelection WRITE setShowSelection NOTIFY showSelectionChanged FINAL REQUIRED)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether to use the compact mode appearance.
|
* @brief Whether to use the compact mode appearance.
|
||||||
*/
|
*/
|
||||||
@@ -161,6 +171,11 @@ public:
|
|||||||
QQmlComponent *quickActionComponent() const;
|
QQmlComponent *quickActionComponent() const;
|
||||||
void setQuickActionComponent(QQmlComponent *quickActionComponent);
|
void setQuickActionComponent(QQmlComponent *quickActionComponent);
|
||||||
|
|
||||||
|
QQmlComponent *selectionComponent() const;
|
||||||
|
void setSelectionComponent(QQmlComponent *selectionComponent);
|
||||||
|
bool showSelection() const;
|
||||||
|
void setShowSelection(bool showSelection);
|
||||||
|
|
||||||
bool showLocalMessagesOnRight() const;
|
bool showLocalMessagesOnRight() const;
|
||||||
void setShowLocalMessagesOnRight(bool showLocalMessagesOnRight);
|
void setShowLocalMessagesOnRight(bool showLocalMessagesOnRight);
|
||||||
|
|
||||||
@@ -182,6 +197,8 @@ Q_SIGNALS:
|
|||||||
void showReadMarkersChanged();
|
void showReadMarkersChanged();
|
||||||
void compactBackgroundComponentChanged();
|
void compactBackgroundComponentChanged();
|
||||||
void quickActionComponentChanged();
|
void quickActionComponentChanged();
|
||||||
|
void selectionComponentChanged();
|
||||||
|
void showSelectionChanged();
|
||||||
void compactModeChanged();
|
void compactModeChanged();
|
||||||
void showLocalMessagesOnRightChanged();
|
void showLocalMessagesOnRightChanged();
|
||||||
void isTemporaryHighlightedChanged();
|
void isTemporaryHighlightedChanged();
|
||||||
@@ -227,6 +244,12 @@ private:
|
|||||||
bool m_quickActionIncubating = false;
|
bool m_quickActionIncubating = false;
|
||||||
QPointer<QQuickItem> m_quickActionItem;
|
QPointer<QQuickItem> m_quickActionItem;
|
||||||
|
|
||||||
|
QPointer<QQmlComponent> m_selectionComponent;
|
||||||
|
bool m_selectionIncubating = false;
|
||||||
|
QPointer<QQuickItem> m_selectionItem;
|
||||||
|
bool m_showSelection = false;
|
||||||
|
void updateSelection();
|
||||||
|
|
||||||
bool m_showLocalMessagesOnRight = true;
|
bool m_showLocalMessagesOnRight = true;
|
||||||
|
|
||||||
bool m_hovered = false;
|
bool m_hovered = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user