Add support for copying & deleting multiple messages at once
BUG: 496458
This commit is contained in:
committed by
Joshua Goins
parent
0f634ff795
commit
f5d726989f
@@ -407,6 +407,13 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
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 {
|
||||
separator: true
|
||||
visible: viewSourceAction.visible
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
|
||||
import Qt.labs.qmlmodels
|
||||
@@ -16,6 +18,11 @@ DelegateChooser {
|
||||
*/
|
||||
required property NeoChatRoom room
|
||||
|
||||
/**
|
||||
* @brief Whether to show selection controls for message delegate.
|
||||
*/
|
||||
property bool showSelectionControl: false
|
||||
|
||||
role: "delegateType"
|
||||
|
||||
DelegateChoice {
|
||||
@@ -25,7 +32,9 @@ DelegateChooser {
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Message
|
||||
delegate: MessageDelegate {}
|
||||
delegate: MessageDelegate {
|
||||
showSelectionControl: root.showSelectionControl
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
|
||||
@@ -5,6 +5,7 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||
@@ -109,6 +110,16 @@ MessageDelegateBase {
|
||||
*/
|
||||
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.timeline: root.ListView.view
|
||||
Message.contentModel: root.contentModel
|
||||
@@ -120,6 +131,7 @@ MessageDelegateBase {
|
||||
enableAvatars: NeoChatConfig?.showAvatarInTimeline ?? false
|
||||
compactMode: NeoChatConfig?.compactLayout ?? false
|
||||
showLocalMessagesOnRight: NeoChatConfig.showLocalMessagesOnRight
|
||||
showSelection: root.showSelectionControl && room.selectedMessageCount > 0
|
||||
|
||||
contentItem: Bubble {
|
||||
id: bubble
|
||||
@@ -230,6 +242,20 @@ MessageDelegateBase {
|
||||
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 {
|
||||
id: _private
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ QQC2.ScrollView {
|
||||
model: root.messageFilterModel
|
||||
delegate: EventDelegate {
|
||||
room: _private.room
|
||||
showSelectionControl: true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
@@ -138,7 +138,10 @@ void MessageDelegateBase::setPercentageValues(bool fillWidth)
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -539,6 +542,77 @@ void MessageDelegateBase::updateQuickAction()
|
||||
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
|
||||
{
|
||||
return m_showLocalMessagesOnRight;
|
||||
@@ -623,10 +697,17 @@ void MessageDelegateBase::resizeContent()
|
||||
nextY += m_sectionItem->implicitHeight() + m_spacing;
|
||||
}
|
||||
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) {
|
||||
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()));
|
||||
yAdd = m_avatarItem->implicitWidth();
|
||||
yAdd = std::max(yAdd, m_avatarItem->implicitHeight());
|
||||
}
|
||||
if (m_contentItem) {
|
||||
const auto contentItemWidth =
|
||||
|
||||
@@ -101,6 +101,16 @@ class MessageDelegateBase : public TimelineDelegate
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -161,6 +171,11 @@ public:
|
||||
QQmlComponent *quickActionComponent() const;
|
||||
void setQuickActionComponent(QQmlComponent *quickActionComponent);
|
||||
|
||||
QQmlComponent *selectionComponent() const;
|
||||
void setSelectionComponent(QQmlComponent *selectionComponent);
|
||||
bool showSelection() const;
|
||||
void setShowSelection(bool showSelection);
|
||||
|
||||
bool showLocalMessagesOnRight() const;
|
||||
void setShowLocalMessagesOnRight(bool showLocalMessagesOnRight);
|
||||
|
||||
@@ -182,6 +197,8 @@ Q_SIGNALS:
|
||||
void showReadMarkersChanged();
|
||||
void compactBackgroundComponentChanged();
|
||||
void quickActionComponentChanged();
|
||||
void selectionComponentChanged();
|
||||
void showSelectionChanged();
|
||||
void compactModeChanged();
|
||||
void showLocalMessagesOnRightChanged();
|
||||
void isTemporaryHighlightedChanged();
|
||||
@@ -227,6 +244,12 @@ private:
|
||||
bool m_quickActionIncubating = false;
|
||||
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_hovered = false;
|
||||
|
||||
Reference in New Issue
Block a user