|
@@ -2,159 +2,490 @@
|
|
|
|
|
|
|
|
#include "appevent.h"
|
|
#include "appevent.h"
|
|
|
#include "widgets/chatView/chat1/chatview.h"
|
|
#include "widgets/chatView/chat1/chatview.h"
|
|
|
|
|
+#include <QTableWidget>
|
|
|
|
|
+#include <QHeaderView>
|
|
|
|
|
+#include <QApplication>
|
|
|
|
|
+#include <QClipboard>
|
|
|
|
|
+#include <QMimeData>
|
|
|
|
|
+#include <QImageReader>
|
|
|
|
|
+#include <QStandardPaths>
|
|
|
|
|
+#include <QDir>
|
|
|
|
|
+#include <QFileInfo>
|
|
|
|
|
+#include <QMessageBox>
|
|
|
|
|
+#include <QScrollBar>
|
|
|
|
|
+#include <QSplitter>
|
|
|
|
|
+#include <QTextDocumentFragment>
|
|
|
|
|
+#include <QTextImageFormat>
|
|
|
|
|
+#include <QMenu>
|
|
|
|
|
+#include <QAction>
|
|
|
|
|
+#include <QUuid>
|
|
|
|
|
|
|
|
-ChatWindow::ChatWindow(WebSocketClient *webSocketClient, QWidget *parent)
|
|
|
|
|
- : QWidget(parent)
|
|
|
|
|
- , m_webSocketClient(webSocketClient)
|
|
|
|
|
|
|
+// MultiFormatInputEdit 实现
|
|
|
|
|
+MultiFormatInputEdit::MultiFormatInputEdit(QWidget *parent)
|
|
|
|
|
+ : QTextEdit(parent)
|
|
|
{
|
|
{
|
|
|
- setWindowTitle("气泡消息示例");
|
|
|
|
|
- resize(800, 600);
|
|
|
|
|
|
|
+ setAcceptDrops(true);
|
|
|
|
|
+ setPlaceholderText("输入消息... (支持粘贴图片)");
|
|
|
|
|
+ setMaximumHeight(120);
|
|
|
|
|
+ setMinimumHeight(60);
|
|
|
|
|
+
|
|
|
|
|
+ // 连接文本变化信号
|
|
|
|
|
+ connect(this, &QTextEdit::textChanged, this, &MultiFormatInputEdit::onTextChanged);
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
|
|
|
|
|
+QString MultiFormatInputEdit::getPlainText() const
|
|
|
|
|
+{
|
|
|
|
|
+ // 获取纯文本,但保留图片占位符信息
|
|
|
|
|
+ QString text = toPlainText().trimmed();
|
|
|
|
|
+ return text;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // 创建消息视图
|
|
|
|
|
- m_messageView = new ChatView(this);
|
|
|
|
|
- mainLayout->addWidget(m_messageView);
|
|
|
|
|
|
|
+QStringList MultiFormatInputEdit::getImagePaths() const
|
|
|
|
|
+{
|
|
|
|
|
+ return m_imagePaths;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // 创建输入区域
|
|
|
|
|
- QHBoxLayout *inputLayout = new QHBoxLayout();
|
|
|
|
|
- m_inputEdit = new QLineEdit(this);
|
|
|
|
|
- m_inputEdit->setPlaceholderText("输入消息...");
|
|
|
|
|
- inputLayout->addWidget(m_inputEdit);
|
|
|
|
|
|
|
+void MultiFormatInputEdit::insertImage(const QString &imagePath)
|
|
|
|
|
+{
|
|
|
|
|
+ if (imagePath.isEmpty() || m_imagePaths.contains(imagePath))
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ m_imagePaths.append(imagePath);
|
|
|
|
|
+ insertImageIntoDocument(imagePath);
|
|
|
|
|
+
|
|
|
|
|
+ emit imageInserted(imagePath);
|
|
|
|
|
+ emit contentChanged();
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- QPushButton *sendButton = new QPushButton("发送", this);
|
|
|
|
|
- inputLayout->addWidget(sendButton);
|
|
|
|
|
-
|
|
|
|
|
- QPushButton *imageButton = new QPushButton("图片", this);
|
|
|
|
|
- // inputLayout->addWidget(imageButton);
|
|
|
|
|
- imageButton->setHidden(true);
|
|
|
|
|
-
|
|
|
|
|
- QPushButton *fileButton = new QPushButton("文件", this);
|
|
|
|
|
- // inputLayout->addWidget(fileButton);
|
|
|
|
|
- fileButton->setHidden(true);
|
|
|
|
|
-
|
|
|
|
|
- QPushButton *recallButton = new QPushButton("撤回", this);
|
|
|
|
|
- // inputLayout->addWidget(recallButton);
|
|
|
|
|
- recallButton->setHidden(true);
|
|
|
|
|
-
|
|
|
|
|
- mainLayout->addLayout(inputLayout);
|
|
|
|
|
-
|
|
|
|
|
- // 连接信号和槽
|
|
|
|
|
- connect(sendButton, &QPushButton::clicked, this, &ChatWindow::onSendClicked);
|
|
|
|
|
- connect(m_inputEdit, &QLineEdit::returnPressed, this, &ChatWindow::onSendClicked);
|
|
|
|
|
- connect(imageButton, &QPushButton::clicked, this, &ChatWindow::onImageClicked);
|
|
|
|
|
- connect(fileButton, &QPushButton::clicked, this, &ChatWindow::onFileClicked);
|
|
|
|
|
- connect(recallButton, &QPushButton::clicked, this, &ChatWindow::onRecallClicked);
|
|
|
|
|
-
|
|
|
|
|
- // // 添加一些示例消息
|
|
|
|
|
- // m_messageView->addMessage(QDateTime::currentDateTime().toString(), "", true);
|
|
|
|
|
- // m_messageView->addMessage("你好,这是一条测试文本消息!", "", true);
|
|
|
|
|
- // m_messageView->addMessage(
|
|
|
|
|
- // "你好,这是一条测试文本消息!你好,这是一条测试文本消息!你好,这是一条测试文本消息!你好,"
|
|
|
|
|
- // "这是一条测试文本消息!你好,这是一条测试文本消息!你好,这是一条测试文本消息!你好,这是一"
|
|
|
|
|
- // "条测试文本消息!你好,这是一条测试文本消息!你好,这是一条测试文本消息!",
|
|
|
|
|
- // "",
|
|
|
|
|
- // true);
|
|
|
|
|
-
|
|
|
|
|
- // initWebsocket();
|
|
|
|
|
- // m_messageView->addSuccessMessage("操作成功完成", BubbleMessage::Sent);
|
|
|
|
|
- // m_messageView->addFailureMessage("操作失败", BubbleMessage::Received);
|
|
|
|
|
-
|
|
|
|
|
- connect(m_webSocketClient,
|
|
|
|
|
- &WebSocketClient::messageReceived,
|
|
|
|
|
- this,
|
|
|
|
|
- [this](const ChatMessage &message) {
|
|
|
|
|
- qDebug() << message.text;
|
|
|
|
|
- if (message.isLeft()) {
|
|
|
|
|
- m_messageView->addMessage(message.text, "", message.senderName, true);
|
|
|
|
|
- } else if (message.isRight()) {
|
|
|
|
|
- m_messageView->addMessage(message.text, "", message.senderName, false);
|
|
|
|
|
- } else {
|
|
|
|
|
- m_messageView->addSystemMessage(message.text);
|
|
|
|
|
- }
|
|
|
|
|
- }); // 回复
|
|
|
|
|
|
|
+void MultiFormatInputEdit::clearContent()
|
|
|
|
|
+{
|
|
|
|
|
+ clear();
|
|
|
|
|
+ m_imagePaths.clear();
|
|
|
|
|
+ m_imageResourceMap.clear();
|
|
|
|
|
+ document()->clear();
|
|
|
|
|
+ emit contentChanged();
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // connect(m_webSocketClient,
|
|
|
|
|
- // &WebSocketClient::connectionStateChanged,
|
|
|
|
|
- // this,
|
|
|
|
|
- // &ChatRoomWidget::onConnectionStateChanged); //连接状态
|
|
|
|
|
- connect(m_webSocketClient,
|
|
|
|
|
- &WebSocketClient::errorOccurred,
|
|
|
|
|
- this,
|
|
|
|
|
- [](const QString &errorMessage) { qDebug() << errorMessage; }); //错误反馈
|
|
|
|
|
|
|
+bool MultiFormatInputEdit::validateContent() const
|
|
|
|
|
+{
|
|
|
|
|
+ QString text = getPlainText();
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否有内容
|
|
|
|
|
+ if (text.isEmpty() && m_imagePaths.isEmpty()) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查文本长度
|
|
|
|
|
+ if (text.length() > 5000) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查图片文件是否存在
|
|
|
|
|
+ for (const QString &imagePath : m_imagePaths) {
|
|
|
|
|
+ if (!QFileInfo::exists(imagePath)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- connect(m_webSocketClient, &WebSocketClient::connectionStateChanged, this, [](bool connected) {
|
|
|
|
|
- emit AppEvent::instance()->connectionStateChanged(connected);
|
|
|
|
|
- });
|
|
|
|
|
|
|
+void MultiFormatInputEdit::removeImage(const QString &imagePath)
|
|
|
|
|
+{
|
|
|
|
|
+ if (!m_imagePaths.contains(imagePath))
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
|
|
+ m_imagePaths.removeAll(imagePath);
|
|
|
|
|
+
|
|
|
|
|
+ // 从文档中移除图片资源
|
|
|
|
|
+ if (m_imageResourceMap.contains(imagePath)) {
|
|
|
|
|
+ QString resourceName = m_imageResourceMap[imagePath];
|
|
|
|
|
+ document()->addResource(QTextDocument::ImageResource, QUrl(resourceName), QVariant());
|
|
|
|
|
+ m_imageResourceMap.remove(imagePath);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ emit imageRemoved(imagePath);
|
|
|
|
|
+ emit contentChanged();
|
|
|
}
|
|
}
|
|
|
-void ChatWindow::initWebsocket(const QString &roomId)
|
|
|
|
|
|
|
+
|
|
|
|
|
+void MultiFormatInputEdit::dragEnterEvent(QDragEnterEvent *event)
|
|
|
{
|
|
{
|
|
|
- // 首先断开链接
|
|
|
|
|
- m_webSocketClient->disconnect();
|
|
|
|
|
|
|
+ if (event->mimeData()->hasImage() || event->mimeData()->hasUrls()) {
|
|
|
|
|
+ event->acceptProposedAction();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ QTextEdit::dragEnterEvent(event);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- if (roomId.isEmpty()) {
|
|
|
|
|
- m_webSocketClient->connectToRoom("416a1990-85ae-48af-aef6-5168230b999f");
|
|
|
|
|
|
|
+void MultiFormatInputEdit::dropEvent(QDropEvent *event)
|
|
|
|
|
+{
|
|
|
|
|
+ const QMimeData *mimeData = event->mimeData();
|
|
|
|
|
+
|
|
|
|
|
+ if (mimeData->hasUrls()) {
|
|
|
|
|
+ const QList<QUrl> urls = mimeData->urls();
|
|
|
|
|
+ for (const QUrl &url : urls) {
|
|
|
|
|
+ if (url.isLocalFile()) {
|
|
|
|
|
+ QString filePath = url.toLocalFile();
|
|
|
|
|
+ if (isImageFormat(filePath)) {
|
|
|
|
|
+ insertImage(filePath);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ event->acceptProposedAction();
|
|
|
|
|
+ } else if (mimeData->hasImage()) {
|
|
|
|
|
+ QPixmap pixmap = qvariant_cast<QPixmap>(mimeData->imageData());
|
|
|
|
|
+ if (!pixmap.isNull()) {
|
|
|
|
|
+ QString tempPath = saveImageToTemp(pixmap);
|
|
|
|
|
+ if (!tempPath.isEmpty()) {
|
|
|
|
|
+ insertImage(tempPath);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ event->acceptProposedAction();
|
|
|
} else {
|
|
} else {
|
|
|
- m_webSocketClient->connectToRoom(roomId);
|
|
|
|
|
|
|
+ QTextEdit::dropEvent(event);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void MultiFormatInputEdit::keyPressEvent(QKeyEvent *event)
|
|
|
|
|
+{
|
|
|
|
|
+ if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
|
|
|
|
|
+ if (event->modifiers() & Qt::ControlModifier) {
|
|
|
|
|
+ // Ctrl+Enter 发送消息
|
|
|
|
|
+ emit sendRequested();
|
|
|
|
|
+ return;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 普通回车换行
|
|
|
|
|
+ QTextEdit::keyPressEvent(event);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ QTextEdit::keyPressEvent(event);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-void ChatWindow::onSendClicked()
|
|
|
|
|
|
|
+void MultiFormatInputEdit::insertFromMimeData(const QMimeData *source)
|
|
|
{
|
|
{
|
|
|
- QString text = m_inputEdit->text().trimmed();
|
|
|
|
|
- if (text.isEmpty())
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ if (source->hasImage()) {
|
|
|
|
|
+ QPixmap pixmap = qvariant_cast<QPixmap>(source->imageData());
|
|
|
|
|
+ if (!pixmap.isNull()) {
|
|
|
|
|
+ QString tempPath = saveImageToTemp(pixmap);
|
|
|
|
|
+ if (!tempPath.isEmpty()) {
|
|
|
|
|
+ insertImage(tempPath);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ QTextEdit::insertFromMimeData(source);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- m_webSocketClient->sendMessage(text, "room");
|
|
|
|
|
|
|
+void MultiFormatInputEdit::contextMenuEvent(QContextMenuEvent *event)
|
|
|
|
|
+{
|
|
|
|
|
+ QMenu *menu = createStandardContextMenu();
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否点击在图片上
|
|
|
|
|
+ QTextCursor cursor = cursorForPosition(event->pos());
|
|
|
|
|
+ QTextCharFormat format = cursor.charFormat();
|
|
|
|
|
+
|
|
|
|
|
+ if (format.isImageFormat()) {
|
|
|
|
|
+ QTextImageFormat imageFormat = format.toImageFormat();
|
|
|
|
|
+ QString imageName = imageFormat.name();
|
|
|
|
|
+
|
|
|
|
|
+ // 查找对应的图片路径
|
|
|
|
|
+ QString imagePath;
|
|
|
|
|
+ for (auto it = m_imageResourceMap.begin(); it != m_imageResourceMap.end(); ++it) {
|
|
|
|
|
+ if (it.value() == imageName) {
|
|
|
|
|
+ imagePath = it.key();
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!imagePath.isEmpty()) {
|
|
|
|
|
+ menu->addSeparator();
|
|
|
|
|
+ QAction *removeAction = menu->addAction("删除图片");
|
|
|
|
|
+ connect(removeAction, &QAction::triggered, [this, imagePath]() {
|
|
|
|
|
+ removeImage(imagePath);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ menu->exec(event->globalPos());
|
|
|
|
|
+ delete menu;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- m_messageView->addMessage(text, "", AppEvent::instance()->userName(), false);
|
|
|
|
|
|
|
+void MultiFormatInputEdit::onTextChanged()
|
|
|
|
|
+{
|
|
|
|
|
+ emit contentChanged();
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- m_inputEdit->clear();
|
|
|
|
|
|
|
+void MultiFormatInputEdit::processImageData(const QByteArray &imageData, const QString &format)
|
|
|
|
|
+{
|
|
|
|
|
+ QPixmap pixmap;
|
|
|
|
|
+ if (pixmap.loadFromData(imageData, format.toUtf8().constData())) {
|
|
|
|
|
+ QString tempPath = saveImageToTemp(pixmap, format);
|
|
|
|
|
+ if (!tempPath.isEmpty()) {
|
|
|
|
|
+ insertImage(tempPath);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void ChatWindow::onImageClicked()
|
|
|
|
|
|
|
+bool MultiFormatInputEdit::isImageFormat(const QString &fileName) const
|
|
|
{
|
|
{
|
|
|
- QString fileName = QFileDialog::getOpenFileName(this,
|
|
|
|
|
- "选择图片",
|
|
|
|
|
- QString(),
|
|
|
|
|
- "图片文件 (*.png *.jpg *.jpeg *.bmp)");
|
|
|
|
|
- if (fileName.isEmpty())
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ QStringList imageFormats = {"png", "jpg", "jpeg", "gif", "bmp", "tiff", "webp"};
|
|
|
|
|
+ QString suffix = QFileInfo(fileName).suffix().toLower();
|
|
|
|
|
+ return imageFormats.contains(suffix);
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- QPixmap image(fileName);
|
|
|
|
|
- if (image.isNull())
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+QString MultiFormatInputEdit::saveImageToTemp(const QPixmap &pixmap, const QString &format)
|
|
|
|
|
+{
|
|
|
|
|
+ QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
|
|
|
|
+ QDir().mkpath(tempDir);
|
|
|
|
|
+
|
|
|
|
|
+ QString fileName = QString("chat_image_%1.%2")
|
|
|
|
|
+ .arg(QDateTime::currentMSecsSinceEpoch())
|
|
|
|
|
+ .arg(format.toLower());
|
|
|
|
|
+ QString filePath = QDir(tempDir).absoluteFilePath(fileName);
|
|
|
|
|
+
|
|
|
|
|
+ if (pixmap.save(filePath, format.toUtf8().constData())) {
|
|
|
|
|
+ return filePath;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return QString();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void ChatWindow::onFileClicked()
|
|
|
|
|
|
|
+void MultiFormatInputEdit::insertImageIntoDocument(const QString &imagePath)
|
|
|
{
|
|
{
|
|
|
- QString fileName = QFileDialog::getOpenFileName(this, "选择文件");
|
|
|
|
|
- if (fileName.isEmpty())
|
|
|
|
|
|
|
+ QPixmap pixmap(imagePath);
|
|
|
|
|
+ if (pixmap.isNull())
|
|
|
return;
|
|
return;
|
|
|
|
|
+
|
|
|
|
|
+ // 缩放图片以适应输入框
|
|
|
|
|
+ int maxWidth = width() - 20; // 留出边距
|
|
|
|
|
+ int maxHeight = 80; // 限制高度
|
|
|
|
|
+
|
|
|
|
|
+ if (pixmap.width() > maxWidth || pixmap.height() > maxHeight) {
|
|
|
|
|
+ pixmap = pixmap.scaled(maxWidth, maxHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 生成唯一的资源名称
|
|
|
|
|
+ QString resourceName = generateResourceName(imagePath);
|
|
|
|
|
+ m_imageResourceMap[imagePath] = resourceName;
|
|
|
|
|
+
|
|
|
|
|
+ // 将图片添加到文档资源
|
|
|
|
|
+ document()->addResource(QTextDocument::ImageResource, QUrl(resourceName), pixmap);
|
|
|
|
|
+
|
|
|
|
|
+ // 在光标位置插入图片
|
|
|
|
|
+ QTextCursor cursor = textCursor();
|
|
|
|
|
+ QTextImageFormat imageFormat;
|
|
|
|
|
+ imageFormat.setName(resourceName);
|
|
|
|
|
+ imageFormat.setWidth(pixmap.width());
|
|
|
|
|
+ imageFormat.setHeight(pixmap.height());
|
|
|
|
|
+
|
|
|
|
|
+ cursor.insertImage(imageFormat);
|
|
|
|
|
+ cursor.insertText(" "); // 在图片后添加空格,方便编辑
|
|
|
|
|
+
|
|
|
|
|
+ setTextCursor(cursor);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+QString MultiFormatInputEdit::generateResourceName(const QString &imagePath)
|
|
|
|
|
+{
|
|
|
|
|
+ return QString("image_%1_%2").arg(QUuid::createUuid().toString(QUuid::WithoutBraces))
|
|
|
|
|
+ .arg(QFileInfo(imagePath).baseName());
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // QFileInfo fileInfo(fileName);
|
|
|
|
|
|
|
+// ChatWindow 实现
|
|
|
|
|
+
|
|
|
|
|
+ChatWindow::ChatWindow(WebSocketClient *webSocketClient, QWidget *parent)
|
|
|
|
|
+ : QWidget(parent)
|
|
|
|
|
+ , m_webSocketClient(webSocketClient)
|
|
|
|
|
+{
|
|
|
|
|
+ setupUI();
|
|
|
|
|
+ connectSignals();
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // // 随机发送或接收文件
|
|
|
|
|
- // BubbleMessage::MessageDirection direction = (qrand() % 2 == 0) ? BubbleMessage::Sent
|
|
|
|
|
- // : BubbleMessage::Received;
|
|
|
|
|
- // m_messageView->addFileMessage(fileInfo.fileName(), fileName, fileInfo.size(), direction);
|
|
|
|
|
|
|
+void ChatWindow::setupUI()
|
|
|
|
|
+{
|
|
|
|
|
+ setWindowTitle("聊天窗口");
|
|
|
|
|
+ resize(800, 600);
|
|
|
|
|
+
|
|
|
|
|
+ QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
|
|
|
+
|
|
|
|
|
+ // 消息显示区域
|
|
|
|
|
+ m_messageView = new ChatView(this);
|
|
|
|
|
+ mainLayout->addWidget(m_messageView, 1);
|
|
|
|
|
+
|
|
|
|
|
+ setupInputArea();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ChatWindow::setupInputArea()
|
|
|
|
|
+{
|
|
|
|
|
+ // 输入区域容器
|
|
|
|
|
+ QWidget *inputContainer = new QWidget;
|
|
|
|
|
+ QVBoxLayout *inputLayout = new QVBoxLayout(inputContainer);
|
|
|
|
|
+ inputLayout->setContentsMargins(5, 5, 5, 5);
|
|
|
|
|
+ inputLayout->setSpacing(5);
|
|
|
|
|
+
|
|
|
|
|
+ // 输入框
|
|
|
|
|
+ m_inputEdit = new MultiFormatInputEdit(this);
|
|
|
|
|
+ inputLayout->addWidget(m_inputEdit);
|
|
|
|
|
+
|
|
|
|
|
+ // 按钮区域
|
|
|
|
|
+ QHBoxLayout *buttonLayout = new QHBoxLayout;
|
|
|
|
|
+ buttonLayout->addStretch();
|
|
|
|
|
+
|
|
|
|
|
+ m_imageButton = new QPushButton("图片", this);
|
|
|
|
|
+ m_fileButton = new QPushButton("文件", this);
|
|
|
|
|
+ m_sendButton = new QPushButton("发送", this);
|
|
|
|
|
+
|
|
|
|
|
+ buttonLayout->addWidget(m_imageButton);
|
|
|
|
|
+ buttonLayout->addWidget(m_fileButton);
|
|
|
|
|
+ buttonLayout->addWidget(m_sendButton);
|
|
|
|
|
+
|
|
|
|
|
+ inputLayout->addLayout(buttonLayout);
|
|
|
|
|
+
|
|
|
|
|
+ // 添加到主布局
|
|
|
|
|
+ layout()->addWidget(inputContainer);
|
|
|
|
|
+
|
|
|
|
|
+ // 隐藏文件按钮(暂未实现)
|
|
|
|
|
+ m_fileButton->hide();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ChatWindow::connectSignals()
|
|
|
|
|
+{
|
|
|
|
|
+ // 输入框信号
|
|
|
|
|
+ connect(m_inputEdit, &MultiFormatInputEdit::sendRequested,
|
|
|
|
|
+ this, &ChatWindow::onSendClicked);
|
|
|
|
|
+ connect(m_inputEdit, &MultiFormatInputEdit::contentChanged,
|
|
|
|
|
+ this, &ChatWindow::onInputContentChanged);
|
|
|
|
|
+ connect(m_inputEdit, &MultiFormatInputEdit::imageInserted,
|
|
|
|
|
+ this, &ChatWindow::onImageInserted);
|
|
|
|
|
+
|
|
|
|
|
+ // 按钮信号
|
|
|
|
|
+ connect(m_sendButton, &QPushButton::clicked, this, &ChatWindow::onSendClicked);
|
|
|
|
|
+ connect(m_imageButton, &QPushButton::clicked, this, &ChatWindow::onImageClicked);
|
|
|
|
|
+ connect(m_fileButton, &QPushButton::clicked, this, &ChatWindow::onFileClicked);
|
|
|
|
|
+
|
|
|
|
|
+ // WebSocket信号
|
|
|
|
|
+ if (m_webSocketClient) {
|
|
|
|
|
+ connect(m_webSocketClient, &WebSocketClient::messageReceived,
|
|
|
|
|
+ [this](const ChatMessage &message) {
|
|
|
|
|
+ if (message.hasImage()) {
|
|
|
|
|
+ // 处理图片消息
|
|
|
|
|
+ if (message.isLeft()) {
|
|
|
|
|
+ m_messageView->addImageMessage(message.imagePath, message.avatar, message.senderName, true, message.text);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ m_messageView->addImageMessage(message.imagePath, message.avatar, message.senderName, false, message.text);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 处理纯文本消息
|
|
|
|
|
+ if (message.isLeft()) {
|
|
|
|
|
+ m_messageView->addMessage(message.text, message.avatar, message.senderName, true);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ m_messageView->addMessage(message.text, message.avatar, message.senderName, false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ connect(m_webSocketClient, &WebSocketClient::connectionStateChanged,
|
|
|
|
|
+ [this](bool connected) {
|
|
|
|
|
+ if (connected) {
|
|
|
|
|
+ m_messageView->addSystemMessage("已连接到服务器");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ m_messageView->addSystemMessage("与服务器断开连接");
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ChatWindow::initWebsocket(const QString &roomId)
|
|
|
|
|
+{
|
|
|
|
|
+ m_roomId = roomId;
|
|
|
|
|
+ if (m_webSocketClient) {
|
|
|
|
|
+ m_webSocketClient->connectToRoom(roomId.isEmpty() ? "default_room" : roomId);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ChatWindow::onSendClicked()
|
|
|
|
|
+{
|
|
|
|
|
+ if (validateInput()) {
|
|
|
|
|
+ sendMessage();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ChatWindow::onImageClicked()
|
|
|
|
|
+{
|
|
|
|
|
+ QString fileName = QFileDialog::getOpenFileName(
|
|
|
|
|
+ this,
|
|
|
|
|
+ "选择图片",
|
|
|
|
|
+ "",
|
|
|
|
|
+ "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif *.tiff *.webp)"
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (!fileName.isEmpty()) {
|
|
|
|
|
+ m_inputEdit->insertImage(fileName);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ChatWindow::onFileClicked()
|
|
|
|
|
+{
|
|
|
|
|
+ // 文件发送功能待实现
|
|
|
|
|
+ QMessageBox::information(this, "提示", "文件发送功能暂未实现");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ChatWindow::onRecallClicked()
|
|
void ChatWindow::onRecallClicked()
|
|
|
{
|
|
{
|
|
|
- // 随机发送或接收撤回消息
|
|
|
|
|
- // BubbleMessage::MessageDirection direction = (qrand() % 2 == 0) ? BubbleMessage::Sent
|
|
|
|
|
- // : BubbleMessage::Received;
|
|
|
|
|
- // QString name = (direction == BubbleMessage::Sent) ? "你" : "对方";
|
|
|
|
|
- // m_messageView->addRecallMessage(name, direction);
|
|
|
|
|
|
|
+ // 撤回消息功能待实现
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ChatWindow::onInputContentChanged()
|
|
|
|
|
+{
|
|
|
|
|
+ // 更新发送按钮状态
|
|
|
|
|
+ m_sendButton->setEnabled(m_inputEdit->validateContent());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ChatWindow::onImageInserted(const QString &imagePath)
|
|
|
|
|
+{
|
|
|
|
|
+ Q_UNUSED(imagePath)
|
|
|
|
|
+ // 图片已直接显示在输入框中,无需额外处理
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ChatWindow::sendMessage()
|
|
|
|
|
+{
|
|
|
|
|
+ QString text = m_inputEdit->getPlainText();
|
|
|
|
|
+ QStringList imagePaths = m_inputEdit->getImagePaths();
|
|
|
|
|
+
|
|
|
|
|
+ // 发送文本消息
|
|
|
|
|
+ if (!text.isEmpty()) {
|
|
|
|
|
+ m_messageView->addMessage(text, "", "我", false);
|
|
|
|
|
+ if (m_webSocketClient) {
|
|
|
|
|
+ m_webSocketClient->sendMessage(text);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 发送图片消息
|
|
|
|
|
+ for (const QString &imagePath : imagePaths) {
|
|
|
|
|
+ sendImageMessage(imagePath, text);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 清空输入框
|
|
|
|
|
+ m_inputEdit->clearContent();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void ChatWindow::sendImageMessage(const QString &imagePath, const QString &text)
|
|
|
|
|
+{
|
|
|
|
|
+ if (QFileInfo::exists(imagePath)) {
|
|
|
|
|
+ m_messageView->addImageMessage(imagePath, "", "我", false, text);
|
|
|
|
|
+ if (m_webSocketClient) {
|
|
|
|
|
+ m_webSocketClient->sendImageMessage(imagePath, text);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool ChatWindow::validateInput() const
|
|
|
|
|
+{
|
|
|
|
|
+ return m_inputEdit->validateContent();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ChatWindow::closeEvent(QCloseEvent *event)
|
|
void ChatWindow::closeEvent(QCloseEvent *event)
|
|
|
{
|
|
{
|
|
|
- // 阻止默认关闭行为
|
|
|
|
|
event->ignore();
|
|
event->ignore();
|
|
|
-
|
|
|
|
|
- // 发出关闭请求信号,让MainPanel处理
|
|
|
|
|
emit windowCloseRequested();
|
|
emit windowCloseRequested();
|
|
|
}
|
|
}
|