1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

feat(chatlog): Add SystemMessages to SessionChatLog

* Rendering of system messages consolidated into single place
* API added to ichatlog to insert a system message at current location
* System messages are now used as type + args which will fit nicely with
  the work in #6221
This commit is contained in:
Mick Sayson 2021-01-26 20:56:22 -08:00
parent bde6dc0df0
commit 916834fb9c
13 changed files with 278 additions and 29 deletions

View File

@ -212,6 +212,13 @@ std::vector<IChatLog::DateChatLogIdxPair> ChatHistory::getDateIdxs(const QDate&
}
}
void ChatHistory::addSystemMessage(const SystemMessage& message)
{
// FIXME: #6221 Insert into history
sessionChatLog.addSystemMessage(message);
}
void ChatHistory::onFileUpdated(const ToxPk& sender, const ToxFile& file)
{
if (canUseHistory()) {

View File

@ -42,12 +42,14 @@ public:
ChatLogIdx getFirstIdx() const override;
ChatLogIdx getNextIdx() const override;
std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, size_t maxDates) const override;
void addSystemMessage(const SystemMessage& message) override;
public slots:
void onFileUpdated(const ToxPk& sender, const ToxFile& file);
void onFileTransferRemotePausedUnpaused(const ToxPk& sender, const ToxFile& file, bool paused);
void onFileTransferBrokenUnbroken(const ToxPk& sender, const ToxFile& file, bool broken);
private slots:
void onMessageReceived(const ToxPk& sender, const Message& message);
void onMessageSent(DispatchedMessageId id, const Message& message);

View File

@ -50,6 +50,11 @@ ChatLogItem::ChatLogItem(ToxPk sender_, const QString& displayName, ChatLogMessa
ChatLogItemDeleter<ChatLogMessage>::doDelete))
{}
ChatLogItem::ChatLogItem(SystemMessage systemMessage)
: contentType(ContentType::systemMessage)
, content(new SystemMessage(std::move(systemMessage)), ChatLogItemDeleter<SystemMessage>::doDelete)
{}
ChatLogItem::ChatLogItem(ToxPk sender_, const QString& displayName_, ContentType contentType_, ContentPtr content_)
: sender(std::move(sender_))
, displayName(displayName_)
@ -91,6 +96,19 @@ const ChatLogMessage& ChatLogItem::getContentAsMessage() const
return *static_cast<ChatLogMessage*>(content.get());
}
SystemMessage& ChatLogItem::getContentAsSystemMessage()
{
assert(contentType == ContentType::systemMessage);
return *static_cast<SystemMessage*>(content.get());
}
const SystemMessage& ChatLogItem::getContentAsSystemMessage() const
{
assert(contentType == ContentType::systemMessage);
return *static_cast<SystemMessage*>(content.get());
}
QDateTime ChatLogItem::getTimestamp() const
{
switch (contentType) {
@ -102,6 +120,10 @@ QDateTime ChatLogItem::getTimestamp() const
const auto& file = getContentAsFile();
return file.timestamp;
}
case ChatLogItem::ContentType::systemMessage: {
const auto& systemMessage = getContentAsSystemMessage();
return systemMessage.timestamp;
}
}
assert(false);

View File

@ -22,6 +22,7 @@
#include "src/core/toxfile.h"
#include "src/core/toxpk.h"
#include "src/model/message.h"
#include "src/model/systemmessage.h"
#include "src/persistence/history.h"
#include <memory>
@ -48,16 +49,20 @@ public:
{
message,
fileTransfer,
systemMessage,
};
ChatLogItem(ToxPk sender, const QString& displayName, ChatLogFile file);
ChatLogItem(ToxPk sender, const QString& displayName, ChatLogMessage message);
ChatLogItem(SystemMessage message);
const ToxPk& getSender() const;
ContentType getContentType() const;
ChatLogFile& getContentAsFile();
const ChatLogFile& getContentAsFile() const;
ChatLogMessage& getContentAsMessage();
const ChatLogMessage& getContentAsMessage() const;
SystemMessage& getContentAsSystemMessage();
const SystemMessage& getContentAsSystemMessage() const;
QDateTime getTimestamp() const;
void setDisplayName(QString name);
const QString& getDisplayName() const;

View File

@ -28,9 +28,9 @@
#include "src/model/chatlogitem.h"
#include "src/model/friend.h"
#include "src/model/group.h"
#include "src/persistence/history.h"
#include "util/strongtype.h"
#include "src/model/systemmessage.h"
#include "src/widget/searchtypes.h"
#include "util/strongtype.h"
#include <cassert>
@ -137,6 +137,12 @@ public:
virtual std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate,
size_t maxDates) const = 0;
/**
* @brief Inserts a system message at the end of the chat
* @param[in] message systemMessage to insert
*/
virtual void addSystemMessage(const SystemMessage& message) = 0;
signals:
void itemUpdated(ChatLogIdx idx);
};

View File

@ -319,6 +319,15 @@ std::vector<IChatLog::DateChatLogIdxPair> SessionChatLog::getDateIdxs(const QDat
return ret;
}
void SessionChatLog::addSystemMessage(const SystemMessage& message)
{
auto messageIdx = nextIdx++;
items.emplace(messageIdx, ChatLogItem(message));
emit this->itemUpdated(messageIdx);
}
void SessionChatLog::insertCompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName,
const ChatLogMessage& message)
{
@ -358,6 +367,13 @@ void SessionChatLog::insertFileAtIdx(ChatLogIdx idx, const ToxPk& sender, QStrin
items.emplace(idx, std::move(item));
}
void SessionChatLog::insertSystemMessageAtIdx(ChatLogIdx idx, SystemMessage message)
{
auto item = ChatLogItem(std::move(message));
items.emplace(idx, std::move(item));
}
/**
* @brief Inserts message data into the chatlog buffer
* @note Owner of SessionChatLog is in charge of attaching this to the appropriate IMessageDispatcher

View File

@ -44,6 +44,7 @@ public:
ChatLogIdx getFirstIdx() const override;
ChatLogIdx getNextIdx() const override;
std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, size_t maxDates) const override;
void addSystemMessage(const SystemMessage& message) override;
void insertCompleteMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName,
const ChatLogMessage& message);
@ -52,6 +53,7 @@ public:
void insertBrokenMessageAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName,
const ChatLogMessage& message);
void insertFileAtIdx(ChatLogIdx idx, const ToxPk& sender, QString senderName, const ChatLogFile& file);
void insertSystemMessageAtIdx(ChatLogIdx idx, SystemMessage message);
public slots:
void onMessageReceived(const ToxPk& sender, const Message& message);

114
src/model/systemmessage.h Normal file
View File

@ -0,0 +1,114 @@
/*
Copyright © 2015-2021 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#include <QDateTime>
#include <QString>
#include <array>
enum class SystemMessageType
{
// DO NOT CHANGE ORDER
// These values are saved directly to the DB and read back, changing the
// order will break persistence!
fileSendFailed = 0,
userJoinedGroup,
userLeftGroup,
peerNameChanged,
peerStateChange,
titleChanged,
cleared,
unexpectedCallEnd,
outgoingCall,
incomingCall,
callEnd,
messageSendFailed,
};
struct SystemMessage
{
using Args = std::array<QString, 4>;
SystemMessageType messageType;
QDateTime timestamp;
Args args;
QString toString() const
{
QString translated;
size_t numArgs = 0;
switch (messageType) {
case SystemMessageType::fileSendFailed:
translated = QObject::tr("Failed to send file \"%1\"");
numArgs = 1;
break;
case SystemMessageType::userJoinedGroup:
translated = QObject::tr("%1 has joined the group");
numArgs = 1;
break;
case SystemMessageType::userLeftGroup:
translated = QObject::tr("%1 has left the group");
numArgs = 1;
break;
case SystemMessageType::peerNameChanged:
translated = QObject::tr("%1 is now known as %2");
numArgs = 2;
break;
case SystemMessageType::titleChanged:
translated = QObject::tr("%1 has set the title to %2");
numArgs = 2;
break;
case SystemMessageType::cleared:
translated = QObject::tr("Cleared");
break;
case SystemMessageType::unexpectedCallEnd:
translated = QObject::tr("Call with %1 ended unexpectedly. %2");
numArgs = 2;
break;
case SystemMessageType::callEnd:
translated = QObject::tr("Call with %1 ended. %2");
numArgs = 2;
break;
case SystemMessageType::peerStateChange:
translated = QObject::tr("%1 is now %2", "e.g. \"Dubslow is now online\"");
numArgs = 2;
break;
case SystemMessageType::outgoingCall:
translated = QObject::tr("Calling %1");
numArgs = 1;
break;
case SystemMessageType::incomingCall:
translated = QObject::tr("%1 calling");
numArgs = 1;
break;
case SystemMessageType::messageSendFailed:
translated = QObject::tr("Message failed to send");
break;
}
for (size_t i = 0; i < numArgs; ++i) {
translated = translated.arg(args[i]);
}
return translated;
}
};

View File

@ -289,9 +289,11 @@ void ChatForm::onAvInvite(uint32_t friendId, bool video)
}
QString displayedName = f->getDisplayedName();
insertChatMessage(ChatMessage::createChatInfoMessage(tr("%1 calling").arg(displayedName),
ChatMessage::INFO,
QDateTime::currentDateTime()));
SystemMessage systemMessage;
systemMessage.messageType = SystemMessageType::incomingCall;
systemMessage.args = {displayedName};
chatLog.addSystemMessage(systemMessage);
auto testedFlag = video ? Settings::AutoAcceptCall::Video : Settings::AutoAcceptCall::Audio;
// AutoAcceptCall is set for this friend
@ -349,8 +351,8 @@ void ChatForm::onAvEnd(uint32_t friendId, bool error)
void ChatForm::showOutgoingCall(bool video)
{
headWidget->showOutgoingCall(video);
addSystemInfoMessage(tr("Calling %1").arg(f->getDisplayedName()), ChatMessage::INFO,
QDateTime::currentDateTime());
addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::outgoingCall,
{f->getDisplayedName()});
emit outgoingNotification();
emit updateFriendActivity(*f);
}
@ -444,10 +446,8 @@ void ChatForm::onFriendStatusChanged(const ToxPk& friendPk, Status::Status statu
if (Settings::getInstance().getStatusChangeNotificationEnabled()) {
QString fStatus = Status::getTitle(status);
addSystemInfoMessage(tr("%1 is now %2", "e.g. \"Dubslow is now online\"")
.arg(f->getDisplayedName())
.arg(fStatus),
ChatMessage::INFO, QDateTime::currentDateTime());
addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::peerStateChange,
{f->getDisplayedName(), fStatus});
}
}
@ -652,10 +652,10 @@ void ChatForm::stopCounter(bool error)
}
QString dhms = secondsToDHMS(timeElapsed.elapsed() / 1000);
QString name = f->getDisplayedName();
QString mess = error ? tr("Call with %1 ended unexpectedly. %2") : tr("Call with %1 ended. %2");
auto messageType = error ? SystemMessageType::unexpectedCallEnd : SystemMessageType::callEnd;
// TODO: add notification once notifications are implemented
addSystemInfoMessage(mess.arg(name, dhms), ChatMessage::INFO, QDateTime::currentDateTime());
addSystemInfoMessage(QDateTime::currentDateTime(), messageType, {name, dhms});
callDurationTimer->stop();
callDuration->setText("");
callDuration->hide();

View File

@ -209,6 +209,32 @@ ChatLogIdx firstItemAfterDate(QDate date, const IChatLog& chatLog)
return chatLog.getNextIdx();
}
}
/**
* @return Chat message message type (info/warning) for the given system message
* @param[in] systemMessage
*/
ChatMessage::SystemMessageType getChatMessageType(const SystemMessage& systemMessage)
{
switch (systemMessage.messageType) {
case SystemMessageType::fileSendFailed:
case SystemMessageType::messageSendFailed:
case SystemMessageType::unexpectedCallEnd:
return ChatMessage::ERROR;
case SystemMessageType::userJoinedGroup:
case SystemMessageType::userLeftGroup:
case SystemMessageType::peerNameChanged:
case SystemMessageType::peerStateChange:
case SystemMessageType::titleChanged:
case SystemMessageType::cleared:
case SystemMessageType::outgoingCall:
case SystemMessageType::incomingCall:
case SystemMessageType::callEnd:
return ChatMessage::INFO;
}
return ChatMessage::INFO;
}
} // namespace
GenericChatForm::GenericChatForm(const Core& _core, const Contact* contact, IChatLog& chatLog,
@ -407,7 +433,42 @@ QDateTime GenericChatForm::getLatestTime() const
if (chatLog.getFirstIdx() == chatLog.getNextIdx())
return QDateTime();
return chatLog.at(chatLog.getNextIdx() - 1).getTimestamp();
const auto shouldUseTimestamp = [this] (ChatLogIdx idx) {
if (chatLog.at(idx).getContentType() != ChatLogItem::ContentType::systemMessage) {
return true;
}
const auto& message = chatLog.at(idx).getContentAsSystemMessage();
switch (message.messageType) {
case SystemMessageType::incomingCall:
case SystemMessageType::outgoingCall:
case SystemMessageType::callEnd:
case SystemMessageType::unexpectedCallEnd:
return true;
case SystemMessageType::cleared:
case SystemMessageType::titleChanged:
case SystemMessageType::peerStateChange:
case SystemMessageType::peerNameChanged:
case SystemMessageType::userLeftGroup:
case SystemMessageType::userJoinedGroup:
case SystemMessageType::fileSendFailed:
case SystemMessageType::messageSendFailed:
return false;
}
qWarning("Unexpected system message type %d", static_cast<int>(message.messageType));
return false;
};
ChatLogIdx idx = chatLog.getNextIdx();
while (idx > chatLog.getFirstIdx()) {
idx = idx - 1;
if (shouldUseTimestamp(idx)) {
return chatLog.at(idx).getTimestamp();
}
}
return QDateTime();
}
void GenericChatForm::reloadTheme()
@ -591,10 +652,14 @@ void GenericChatForm::setColorizedNames(bool enable)
colorizeNames = enable;
}
void GenericChatForm::addSystemInfoMessage(const QString& message, ChatMessage::SystemMessageType type,
const QDateTime& datetime)
void GenericChatForm::addSystemInfoMessage(const QDateTime& datetime, SystemMessageType messageType,
SystemMessage::Args messageArgs)
{
insertChatMessage(ChatMessage::createChatInfoMessage(message, type, datetime));
SystemMessage systemMessage;
systemMessage.messageType = static_cast<SystemMessageType>(messageType);
systemMessage.timestamp = datetime;
systemMessage.args = std::move(messageArgs);
chatLog.addSystemMessage(systemMessage);
}
void GenericChatForm::addSystemDateMessage(const QDate& date)
@ -759,7 +824,7 @@ void GenericChatForm::clearChatArea(bool confirm, bool inform)
chatWidget->clear();
if (inform)
addSystemInfoMessage(tr("Cleared"), ChatMessage::INFO, QDateTime::currentDateTime());
addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::cleared, {});
messages.clear();
}
@ -1070,6 +1135,17 @@ void GenericChatForm::renderItem(const ChatLogItem& item, bool hideName, bool co
renderFile(item.getDisplayName(), file.file, isSelf, item.getTimestamp(), chatMessage);
break;
}
case ChatLogItem::ContentType::systemMessage: {
const auto& systemMessage = item.getContentAsSystemMessage();
auto chatMessageType = getChatMessageType(systemMessage);
chatMessage = ChatMessage::createChatInfoMessage(systemMessage.toString(), chatMessageType,
QDateTime::currentDateTime());
// Ignore caller's decision to hide the name. We show the icon in the
// slot of the sender's name so we always want it visible
hideName = false;
break;
}
}
if (hideName) {

View File

@ -76,8 +76,8 @@ public:
void setName(const QString& newName);
virtual void show(ContentLayout* contentLayout);
void addSystemInfoMessage(const QString& message, ChatMessage::SystemMessageType type,
const QDateTime& datetime);
void addSystemInfoMessage(const QDateTime& datetime, SystemMessageType messageType,
SystemMessage::Args messageArgs);
static QString resolveToxPk(const ToxPk& pk);
QDateTime getLatestTime() const;

View File

@ -148,9 +148,8 @@ void GroupChatForm::onTitleChanged(const QString& author, const QString& title)
return;
}
const QString message = tr("%1 has set the title to %2").arg(author, title);
const QDateTime curTime = QDateTime::currentDateTime();
addSystemInfoMessage(message, ChatMessage::INFO, curTime);
addSystemInfoMessage(curTime, SystemMessageType::titleChanged, {author, title});
}
void GroupChatForm::onScreenshotClicked()
@ -232,19 +231,20 @@ void GroupChatForm::updateUserNames()
void GroupChatForm::onUserJoined(const ToxPk& user, const QString& name)
{
addSystemInfoMessage(tr("%1 has joined the group").arg(name), ChatMessage::INFO, QDateTime::currentDateTime());
addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::userJoinedGroup, {name});
updateUserNames();
}
void GroupChatForm::onUserLeft(const ToxPk& user, const QString& name)
{
addSystemInfoMessage(tr("%1 has left the group").arg(name), ChatMessage::INFO, QDateTime::currentDateTime());
addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::userLeftGroup, {name});
updateUserNames();
}
void GroupChatForm::onPeerNameChanged(const ToxPk& peer, const QString& oldName, const QString& newName)
{
addSystemInfoMessage(tr("%1 is now known as %2").arg(oldName, newName), ChatMessage::INFO, QDateTime::currentDateTime());
addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::peerNameChanged,
{oldName, newName});
updateUserNames();
}

View File

@ -1130,8 +1130,8 @@ void Widget::dispatchFileSendFailed(uint32_t friendId, const QString& fileName)
return;
}
chatForm.value()->addSystemInfoMessage(tr("Failed to send file \"%1\"").arg(fileName),
ChatMessage::ERROR, QDateTime::currentDateTime());
chatForm.value()->addSystemInfoMessage(QDateTime::currentDateTime(),
SystemMessageType::fileSendFailed, {fileName});
}
void Widget::onRejectCall(uint32_t friendId)
@ -2342,10 +2342,9 @@ void Widget::onGroupSendFailed(uint32_t groupnumber)
const auto& groupId = GroupList::id2Key(groupnumber);
assert(GroupList::findGroup(groupId));
const auto message = tr("Message failed to send");
const auto curTime = QDateTime::currentDateTime();
auto form = groupChatForms[groupId].data();
form->addSystemInfoMessage(message, ChatMessage::INFO, curTime);
form->addSystemInfoMessage(curTime, SystemMessageType::messageSendFailed, {});
}
void Widget::onFriendTypingChanged(uint32_t friendnumber, bool isTyping)