162 lines
4.6 KiB
C++
162 lines
4.6 KiB
C++
|
|
#include "chat_widget.h"
|
||
|
|
#include <QVBoxLayout>
|
||
|
|
#include <QHBoxLayout>
|
||
|
|
#include <QSqlQuery>
|
||
|
|
#include <QSqlError>
|
||
|
|
#include <QTimer>
|
||
|
|
#include <QDateTime>
|
||
|
|
#include <QStandardPaths>
|
||
|
|
#include <QDir>
|
||
|
|
#include <iostream>
|
||
|
|
|
||
|
|
namespace scar {
|
||
|
|
|
||
|
|
ChatWidget::ChatWidget(QWidget* parent)
|
||
|
|
: QWidget(parent), isTyping_(false) {
|
||
|
|
|
||
|
|
setupDatabase();
|
||
|
|
setupUI();
|
||
|
|
|
||
|
|
// Typing indicator timer
|
||
|
|
typingTimer_ = new QTimer(this);
|
||
|
|
typingTimer_->setSingleShot(true);
|
||
|
|
connect(typingTimer_, &QTimer::timeout, [this]() {
|
||
|
|
if (isTyping_) {
|
||
|
|
isTyping_ = false;
|
||
|
|
emit typingIndicator(false);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
ChatWidget::~ChatWidget() {
|
||
|
|
if (db_.isOpen()) {
|
||
|
|
db_.close();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void ChatWidget::setupUI() {
|
||
|
|
auto* layout = new QVBoxLayout(this);
|
||
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
||
|
|
|
||
|
|
// Chat display
|
||
|
|
chatDisplay_ = new QTextEdit(this);
|
||
|
|
chatDisplay_->setReadOnly(true);
|
||
|
|
chatDisplay_->setStyleSheet("QTextEdit { border: none; }");
|
||
|
|
layout->addWidget(chatDisplay_);
|
||
|
|
|
||
|
|
// Input area
|
||
|
|
auto* inputLayout = new QHBoxLayout();
|
||
|
|
|
||
|
|
messageInput_ = new QLineEdit(this);
|
||
|
|
messageInput_->setPlaceholderText("Type a message...");
|
||
|
|
connect(messageInput_, &QLineEdit::returnPressed, this, &ChatWidget::onSendClicked);
|
||
|
|
connect(messageInput_, &QLineEdit::textChanged, this, &ChatWidget::onTextChanged);
|
||
|
|
inputLayout->addWidget(messageInput_);
|
||
|
|
|
||
|
|
sendButton_ = new QPushButton("Send", this);
|
||
|
|
connect(sendButton_, &QPushButton::clicked, this, &ChatWidget::onSendClicked);
|
||
|
|
inputLayout->addWidget(sendButton_);
|
||
|
|
|
||
|
|
layout->addLayout(inputLayout);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ChatWidget::setupDatabase() {
|
||
|
|
QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||
|
|
QDir().mkpath(dataDir);
|
||
|
|
|
||
|
|
db_ = QSqlDatabase::addDatabase("QSQLITE", "chat_history");
|
||
|
|
db_.setDatabaseName(dataDir + "/chat_history.db");
|
||
|
|
|
||
|
|
if (!db_.open()) {
|
||
|
|
std::cerr << "Failed to open chat history database" << std::endl;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
QSqlQuery query(db_);
|
||
|
|
query.exec(R"(
|
||
|
|
CREATE TABLE IF NOT EXISTS messages (
|
||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
|
|
sender TEXT NOT NULL,
|
||
|
|
content TEXT NOT NULL,
|
||
|
|
timestamp INTEGER NOT NULL
|
||
|
|
)
|
||
|
|
)");
|
||
|
|
}
|
||
|
|
|
||
|
|
void ChatWidget::onSendClicked() {
|
||
|
|
QString message = messageInput_->text().trimmed();
|
||
|
|
if (message.isEmpty()) return;
|
||
|
|
|
||
|
|
emit messageSent(message);
|
||
|
|
addMessage("You", message, true);
|
||
|
|
messageInput_->clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
void ChatWidget::onTextChanged() {
|
||
|
|
if (!messageInput_->text().isEmpty() && !isTyping_) {
|
||
|
|
isTyping_ = true;
|
||
|
|
emit typingIndicator(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Reset typing timer
|
||
|
|
typingTimer_->start(2000); // Stop typing indicator after 2 seconds
|
||
|
|
}
|
||
|
|
|
||
|
|
void ChatWidget::addMessage(const QString& sender, const QString& content, bool isOwnMessage) {
|
||
|
|
QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
|
||
|
|
QString color = isOwnMessage ? "#5865F2" : "#43B581"; // Blue for own, green for others
|
||
|
|
|
||
|
|
QString html = QString("<p style='margin: 5px 0;'>"
|
||
|
|
"<span style='color: %1; font-weight: bold;'>%2</span> "
|
||
|
|
"<span style='color: #888; font-size: 10px;'>%3</span><br>"
|
||
|
|
"%4</p>")
|
||
|
|
.arg(color, sender, timestamp, content);
|
||
|
|
|
||
|
|
chatDisplay_->append(html);
|
||
|
|
|
||
|
|
// Save to database
|
||
|
|
if (!isOwnMessage) {
|
||
|
|
saveMessage(sender, content);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void ChatWidget::clear() {
|
||
|
|
chatDisplay_->clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
void ChatWidget::loadHistory() {
|
||
|
|
if (!db_.isOpen()) return;
|
||
|
|
|
||
|
|
QSqlQuery query(db_);
|
||
|
|
query.prepare("SELECT sender, content FROM messages ORDER BY timestamp DESC LIMIT 100");
|
||
|
|
|
||
|
|
if (query.exec()) {
|
||
|
|
QStringList messages;
|
||
|
|
while (query.next()) {
|
||
|
|
messages.prepend(query.value(0).toString() + ": " + query.value(1).toString());
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const QString& msg : messages) {
|
||
|
|
int colonPos = msg.indexOf(": ");
|
||
|
|
if (colonPos > 0) {
|
||
|
|
QString sender = msg.left(colonPos);
|
||
|
|
QString content = msg.mid(colonPos + 2);
|
||
|
|
addMessage(sender, content, false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void ChatWidget::saveMessage(const QString& sender, const QString& content) {
|
||
|
|
if (!db_.isOpen()) return;
|
||
|
|
|
||
|
|
QSqlQuery query(db_);
|
||
|
|
query.prepare("INSERT INTO messages (sender, content, timestamp) VALUES (?, ?, ?)");
|
||
|
|
query.addBindValue(sender);
|
||
|
|
query.addBindValue(content);
|
||
|
|
query.addBindValue(QDateTime::currentSecsSinceEpoch());
|
||
|
|
query.exec();
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace scar
|