Edit group text in desktop composer with save cancel and draft restoration

This commit is contained in:
evgeny committed 2026-10-26 22:38:06 +01:00
1 parent b46927f40d
commit e483027e3e
2 files changed
+55 -2

No files matched your search

+48 -2
View File
@@ -102,7 +102,7 @@ InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *par
auto* cancelReply = new QPushButton(QString::fromUtf8("✕"), m_replyPreview);
cancelReply->setToolTip(QCoreApplication::translate("ChatGui", "Cancel reply"));
cancelReply->setFixedSize(24, 24); replyLayout->addWidget(cancelReply);
connect(cancelReply, &QPushButton::clicked, this, &InputBar::clearReply);
connect(cancelReply, &QPushButton::clicked, this, [this] { if (m_editId.isEmpty()) clearReply(); else cancelEdit(); });
mainLayout->addWidget(m_replyPreview); m_replyPreview->hide();
/* Attachment preview bar (hidden until file selected) */
@@ -168,6 +168,7 @@ InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *par
auto *sendBtn = makeIconBtn(QIcon(":/icons/send.png"),
QCoreApplication::translate("ChatGui", "Send"), this);
m_sendBtn = sendBtn;
sendBtn->setStyleSheet(sendBtn->styleSheet() +
"QPushButton { border: 1px solid palette(highlight); }");
layout->addWidget(sendBtn, 0, Qt::AlignBottom);
@@ -250,6 +251,7 @@ InputBar::~InputBar() {
/* Смена цели создаёт новую ревизию: поздний результат не очищает следующий ответ. */
void InputBar::setReply(const QByteArray& messageId, const QString& author, const QString& preview) {
if (!m_editId.isEmpty()) cancelEdit();
if (m_dmMode || messageId.size() != 32 || m_channelIdForRecord.isEmpty()) {
GUI_ERROR("reply: invalid composer target/key bytes=%d", int(messageId.size())); return;
}
@@ -282,6 +284,15 @@ quint64 InputBar::beginReplySend(const QByteArray& reply, quint64 generation, co
}
void InputBar::submissionResult(quint64 request, bool success) {
if (request && request == m_editRequest) {
m_editRequest = 0; m_input->setReadOnly(false);
if (success) cancelEdit();
else {
GUI_ERROR("edit: save failed target=%s", qPrintable(m_channelIdForRecord));
emit attachmentRejected(QCoreApplication::translate("ChatGui", "Could not save edit. The message may have changed. Reopen it to retry."));
}
return;
}
if (!request || !m_pendingReplies.contains(request)) return;
const auto pending = m_pendingReplies.take(request);
GUI_DEBUG("reply: result ch=%s request=%llu success=%d", qPrintable(pending.target), (unsigned long long)request, success);
@@ -297,6 +308,32 @@ void InputBar::submissionResult(quint64 request, bool success) {
if (!replySending() && !m_replyFailed && m_input->toPlainText().isEmpty() && m_pendingAttachSrc.isEmpty()) clearReply();
}
/* Черновик и выбранный ответ восстанавливаются после сохранения/отмены. Вложения остаются в composer. */
void InputBar::editMessage(const QByteArray& id, quint64 version, const QString& text) {
if (m_dmMode || id.size() != 32 || !version || m_editRequest || replySending() || m_pttActive) {
GUI_WARN("edit: composer busy/invalid target=%s", qPrintable(m_channelIdForRecord)); return;
}
if (!m_editId.isEmpty()) cancelEdit();
m_savedText = m_input->toPlainText(); m_savedReply = m_replyTo; m_savedReplyLabel = m_replyLabel->text();
clearReply(); m_editId = id; m_editVersion = version;
m_replyLabel->setText(QCoreApplication::translate("ChatGui", "Editing message")); m_replyPreview->show();
m_attachPreview->hide(); m_attachBtn->setEnabled(false); m_pttBtn->setEnabled(false);
m_sendBtn->setToolTip(QCoreApplication::translate("ChatGui", "Save edit"));
m_input->setPlainText(text); m_input->setFocus();
GUI_INFO("edit: opened target=%s id=%s ver=%llu", qPrintable(m_channelIdForRecord), id.toHex().constData(),
(unsigned long long)version);
}
void InputBar::cancelEdit() {
if (m_editId.isEmpty()) return;
m_editId.clear(); m_editVersion = 0; m_editRequest = 0; m_input->setReadOnly(false);
m_input->setPlainText(m_savedText); m_savedText.clear();
m_replyTo = m_savedReply; m_savedReply.clear(); m_replyLabel->setText(m_savedReplyLabel); m_savedReplyLabel.clear();
m_replyPreview->setVisible(!m_replyTo.isEmpty()); m_attachPreview->setVisible(!m_pendingAttachSrc.isEmpty());
m_attachBtn->setEnabled(true); m_pttBtn->setEnabled(true);
m_sendBtn->setToolTip(QCoreApplication::translate("ChatGui", "Send"));
}
/* Qt доставляет drop в viewport, а не в QTextEdit/QListView. Родитель composer принимает drop на кнопках. */
void InputBar::installFileDropTarget(QWidget* target) {
if (!target || m_dropTargets.contains(target)) return;
@@ -308,7 +345,7 @@ void InputBar::installFileDropTarget(QWidget* target) {
}
bool InputBar::canAttachFile() const {
return isEnabled() && !m_channelIdForRecord.isEmpty() && m_attachBtn->isEnabled() &&
return isEnabled() && m_editId.isEmpty() && !m_channelIdForRecord.isEmpty() && m_attachBtn->isEnabled() &&
!m_pttActive && (!m_recorder || !m_recorder->isRecording());
}
@@ -367,6 +404,7 @@ bool InputBar::eventFilter(QObject *obj, QEvent *event) {
}
if (obj == m_input && event->type() == QEvent::KeyPress) {
auto *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_Escape && !m_editId.isEmpty()) { cancelEdit(); return true; }
if (ke->key() == Qt::Key_Escape && !m_replyTo.isEmpty()) { clearReply(); return true; }
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
if (ke->modifiers() & Qt::ControlModifier) {
@@ -404,6 +442,12 @@ void InputBar::adjustInputHeight() {
void InputBar::onSendClicked() {
if (!isEnabled()) { GUI_WARN("send: composer unavailable target=%s", qPrintable(m_channelIdForRecord)); return; }
if (!m_editId.isEmpty()) {
const QString text = m_input->toPlainText();
if (m_editRequest || text.isEmpty()) return;
m_editRequest = ++m_submissionSequence; m_input->setReadOnly(true);
emit saveEdit(m_channelIdForRecord, m_editId, m_editVersion, text, m_editRequest); return;
}
if (replySending()) {
GUI_WARN("reply: submission still pending ch=%s", qPrintable(m_channelIdForRecord)); return;
}
@@ -476,6 +520,7 @@ void InputBar::setRecordingContext(const QString& mediaDirBase, const QString& c
if (m_channelIdForRecord == channelId && m_dmGeneration && m_dmGeneration != dmGeneration)
forgetDmHistory(channelId, m_dmGeneration);
if (m_channelIdForRecord != channelId || m_mediaDirBase != mediaDirBase || m_dmGeneration != dmGeneration) {
cancelEdit();
clearReply();
++m_attachmentContext;
clearPendingAttachment();
@@ -522,6 +567,7 @@ void InputBar::setMediaEnabled(bool enabled) {
}
void InputBar::onPttPressed() {
if (!m_editId.isEmpty()) { GUI_WARN("voice: blocked while editing"); return; }
if (!isEnabled() || m_channelIdForRecord.isEmpty()) { GUI_WARN("voice: composer unavailable"); return; }
if (replySending()) { GUI_WARN("reply: recording blocked while submission pending"); return; }
if (!m_recorder || !m_recorder->isInitialized()) return;
+7
View File
@@ -38,9 +38,12 @@ public:
void setReply(const QByteArray& messageId, const QString& author, const QString& preview);
void clearReply();
void submissionResult(quint64 request, bool success);
void editMessage(const QByteArray& id, quint64 version, const QString& text);
void cancelEdit();
signals:
void sendMessage(const QString &text, const QByteArray& replyTo, quint64 request);
void saveEdit(const QString& channel, const QByteArray& id, quint64 version, const QString& text, quint64 request);
void submitAttachment(const QString& target, struct attachment_send_req* request);
void sendFileAttachment(const QString& target, const QString& mediaSrc, const QString& mediaDest,
const QString& displayName, const QByteArray& replyTo, quint64 request);
@@ -74,6 +77,10 @@ private:
quint64 beginReplySend(const QByteArray& reply, quint64 generation, const QString& target,
const QString& text = {}, const QString& source = {});
bool replySending() const;
QByteArray m_editId, m_savedReply;
QString m_savedText, m_savedReplyLabel;
quint64 m_editVersion = 0, m_editRequest = 0;
QPushButton* m_sendBtn = nullptr;
QWidget* m_replyPreview = nullptr;
QLabel* m_replyLabel = nullptr;
QByteArray m_replyTo, m_recordReply;