|
|
@@ -1,24 +1,26 @@
|
|
|
#include "chatwindow.h"
|
|
|
|
|
|
-#include "appevent.h"
|
|
|
-#include "widgets/chatView/chat1/chatview.h"
|
|
|
-#include <QTableWidget>
|
|
|
-#include <QHeaderView>
|
|
|
+#include <QAction>
|
|
|
#include <QApplication>
|
|
|
+#include <QButtonGroup>
|
|
|
#include <QClipboard>
|
|
|
-#include <QMimeData>
|
|
|
-#include <QImageReader>
|
|
|
-#include <QStandardPaths>
|
|
|
#include <QDir>
|
|
|
#include <QFileInfo>
|
|
|
+#include <QHeaderView>
|
|
|
+#include <QImageReader>
|
|
|
+#include <QMenu>
|
|
|
#include <QMessageBox>
|
|
|
+#include <QMimeData>
|
|
|
#include <QScrollBar>
|
|
|
#include <QSplitter>
|
|
|
+#include <QStandardPaths>
|
|
|
+#include <QTableWidget>
|
|
|
#include <QTextDocumentFragment>
|
|
|
#include <QTextImageFormat>
|
|
|
-#include <QMenu>
|
|
|
-#include <QAction>
|
|
|
#include <QUuid>
|
|
|
+#include "appevent.h"
|
|
|
+#include "qpushbutton.h"
|
|
|
+#include "widgets/chatView/chat1/chatview.h"
|
|
|
|
|
|
// MultiFormatInputEdit 实现
|
|
|
MultiFormatInputEdit::MultiFormatInputEdit(QWidget *parent)
|
|
|
@@ -28,7 +30,7 @@ MultiFormatInputEdit::MultiFormatInputEdit(QWidget *parent)
|
|
|
setPlaceholderText("输入消息... (支持粘贴图片)");
|
|
|
setMaximumHeight(120);
|
|
|
setMinimumHeight(60);
|
|
|
-
|
|
|
+
|
|
|
// 连接文本变化信号
|
|
|
connect(this, &QTextEdit::textChanged, this, &MultiFormatInputEdit::onTextChanged);
|
|
|
}
|
|
|
@@ -49,10 +51,10 @@ 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();
|
|
|
}
|
|
|
@@ -69,24 +71,24 @@ void MultiFormatInputEdit::clearContent()
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
@@ -94,16 +96,16 @@ 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();
|
|
|
}
|
|
|
@@ -120,7 +122,7 @@ void MultiFormatInputEdit::dragEnterEvent(QDragEnterEvent *event)
|
|
|
void MultiFormatInputEdit::dropEvent(QDropEvent *event)
|
|
|
{
|
|
|
const QMimeData *mimeData = event->mimeData();
|
|
|
-
|
|
|
+
|
|
|
if (mimeData->hasUrls()) {
|
|
|
const QList<QUrl> urls = mimeData->urls();
|
|
|
for (const QUrl &url : urls) {
|
|
|
@@ -180,15 +182,15 @@ void MultiFormatInputEdit::insertFromMimeData(const QMimeData *source)
|
|
|
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) {
|
|
|
@@ -197,7 +199,7 @@ void MultiFormatInputEdit::contextMenuEvent(QContextMenuEvent *event)
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (!imagePath.isEmpty()) {
|
|
|
menu->addSeparator();
|
|
|
QAction *removeAction = menu->addAction("删除图片");
|
|
|
@@ -206,7 +208,7 @@ void MultiFormatInputEdit::contextMenuEvent(QContextMenuEvent *event)
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
menu->exec(event->globalPos());
|
|
|
delete menu;
|
|
|
}
|
|
|
@@ -238,16 +240,15 @@ QString MultiFormatInputEdit::saveImageToTemp(const QPixmap &pixmap, const QStri
|
|
|
{
|
|
|
QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
|
|
QDir().mkpath(tempDir);
|
|
|
-
|
|
|
- QString fileName = QString("chat_image_%1.%2")
|
|
|
- .arg(QDateTime::currentMSecsSinceEpoch())
|
|
|
- .arg(format.toLower());
|
|
|
+
|
|
|
+ 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();
|
|
|
}
|
|
|
|
|
|
@@ -256,39 +257,40 @@ void MultiFormatInputEdit::insertImageIntoDocument(const QString &imagePath)
|
|
|
QPixmap pixmap(imagePath);
|
|
|
if (pixmap.isNull())
|
|
|
return;
|
|
|
-
|
|
|
+
|
|
|
// 缩放图片以适应输入框
|
|
|
- int maxWidth = width() - 20; // 留出边距
|
|
|
- int maxHeight = 80; // 限制高度
|
|
|
-
|
|
|
+ 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());
|
|
|
+ return QString("image_%1_%2")
|
|
|
+ .arg(QUuid::createUuid().toString(QUuid::WithoutBraces))
|
|
|
+ .arg(QFileInfo(imagePath).baseName());
|
|
|
}
|
|
|
|
|
|
// ChatWindow 实现
|
|
|
@@ -296,7 +298,13 @@ QString MultiFormatInputEdit::generateResourceName(const QString &imagePath)
|
|
|
ChatWindow::ChatWindow(WebSocketClient *webSocketClient, QWidget *parent)
|
|
|
: QWidget(parent)
|
|
|
, m_webSocketClient(webSocketClient)
|
|
|
+ , m_quickButtonContainer(nullptr)
|
|
|
+ , m_hideTimer(new QTimer(this))
|
|
|
{
|
|
|
+ // 设置定时器为单次触发
|
|
|
+ m_hideTimer->setSingleShot(true);
|
|
|
+ connect(m_hideTimer, &QTimer::timeout, this, &ChatWindow::hideQuickButtons);
|
|
|
+
|
|
|
setupUI();
|
|
|
connectSignals();
|
|
|
}
|
|
|
@@ -305,13 +313,13 @@ void ChatWindow::setupUI()
|
|
|
{
|
|
|
setWindowTitle("聊天窗口");
|
|
|
resize(800, 600);
|
|
|
-
|
|
|
+
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
|
-
|
|
|
+
|
|
|
// 消息显示区域
|
|
|
m_messageView = new ChatView(this);
|
|
|
mainLayout->addWidget(m_messageView, 1);
|
|
|
-
|
|
|
+
|
|
|
setupInputArea();
|
|
|
}
|
|
|
|
|
|
@@ -322,28 +330,46 @@ void ChatWindow::setupInputArea()
|
|
|
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();
|
|
|
+
|
|
|
+ QButtonGroup *group = new QButtonGroup(this);
|
|
|
+ m_quickButtonGroup = group; // 保存引用以便后续连接信号
|
|
|
+ QHBoxLayout *quickButtons = new QHBoxLayout;
|
|
|
+ quickButtons->addSpacing(5);
|
|
|
+ for (int var = 0; var < 7; ++var) {
|
|
|
+ auto button = new QPushButton(QString::number(var), this);
|
|
|
+ group->addButton(button);
|
|
|
+ quickButtons->addWidget(button);
|
|
|
+ }
|
|
|
|
|
|
+ // 创建快捷按钮容器并设置初始状态为隐藏
|
|
|
+ m_quickButtonContainer = new QWidget(this);
|
|
|
+ m_quickButtonContainer->setLayout(quickButtons);
|
|
|
+ m_quickButtonContainer->setVisible(false);
|
|
|
+
|
|
|
m_imageButton = new QPushButton("图片", this);
|
|
|
m_fileButton = new QPushButton("文件", this);
|
|
|
+ m_streamButton = new QPushButton("推流", this);
|
|
|
m_sendButton = new QPushButton("发送", this);
|
|
|
-
|
|
|
+
|
|
|
buttonLayout->addWidget(m_imageButton);
|
|
|
buttonLayout->addWidget(m_fileButton);
|
|
|
+ buttonLayout->addWidget(m_streamButton);
|
|
|
buttonLayout->addWidget(m_sendButton);
|
|
|
-
|
|
|
+
|
|
|
+ inputLayout->addWidget(m_quickButtonContainer);
|
|
|
+ inputLayout->addWidget(m_inputEdit);
|
|
|
inputLayout->addLayout(buttonLayout);
|
|
|
-
|
|
|
+
|
|
|
// 添加到主布局
|
|
|
layout()->addWidget(inputContainer);
|
|
|
-
|
|
|
+
|
|
|
// 隐藏文件按钮(暂未实现)
|
|
|
m_fileButton->hide();
|
|
|
}
|
|
|
@@ -351,47 +377,73 @@ void ChatWindow::setupInputArea()
|
|
|
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_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);
|
|
|
-
|
|
|
+ connect(m_streamButton, &QPushButton::clicked, this, &ChatWindow::onStreamButtonClicked);
|
|
|
+
|
|
|
+ // 快速按钮信号连接
|
|
|
+ if (m_quickButtonGroup) {
|
|
|
+ const QList<QAbstractButton *> buttons = m_quickButtonGroup->buttons();
|
|
|
+ for (QAbstractButton *button : buttons) {
|
|
|
+ connect(button, &QAbstractButton::clicked, this, &ChatWindow::onQuickButtonClicked);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// WebSocket信号
|
|
|
if (m_webSocketClient) {
|
|
|
- connect(m_webSocketClient, &WebSocketClient::messageReceived,
|
|
|
+ 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);
|
|
|
+ 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);
|
|
|
+ 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);
|
|
|
+ m_messageView->addMessage(message.text,
|
|
|
+ message.avatar,
|
|
|
+ message.senderName,
|
|
|
+ true);
|
|
|
} else {
|
|
|
- m_messageView->addMessage(message.text, message.avatar, message.senderName, false);
|
|
|
+ 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("与服务器断开连接");
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- connect(m_webSocketClient, &WebSocketClient::connectionStateChanged,
|
|
|
- [this](bool connected) {
|
|
|
- if (connected) {
|
|
|
- m_messageView->addSystemMessage("已连接到服务器");
|
|
|
- } else {
|
|
|
- m_messageView->addSystemMessage("与服务器断开连接");
|
|
|
- }
|
|
|
- });
|
|
|
+ // 连接推流通知信号
|
|
|
+ connect(m_webSocketClient, &WebSocketClient::streamNotification, this, &ChatWindow::onStreamNotification);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -412,13 +464,12 @@ void ChatWindow::onSendClicked()
|
|
|
|
|
|
void ChatWindow::onImageClicked()
|
|
|
{
|
|
|
- QString fileName = QFileDialog::getOpenFileName(
|
|
|
- this,
|
|
|
- "选择图片",
|
|
|
- "",
|
|
|
- "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif *.tiff *.webp)"
|
|
|
- );
|
|
|
-
|
|
|
+ QString fileName
|
|
|
+ = QFileDialog::getOpenFileName(this,
|
|
|
+ "选择图片",
|
|
|
+ "",
|
|
|
+ "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif *.tiff *.webp)");
|
|
|
+
|
|
|
if (!fileName.isEmpty()) {
|
|
|
m_inputEdit->insertImage(fileName);
|
|
|
}
|
|
|
@@ -447,11 +498,67 @@ void ChatWindow::onImageInserted(const QString &imagePath)
|
|
|
// 图片已直接显示在输入框中,无需额外处理
|
|
|
}
|
|
|
|
|
|
+void ChatWindow::onQuickButtonClicked()
|
|
|
+{
|
|
|
+ QPushButton *button = qobject_cast<QPushButton *>(sender());
|
|
|
+ if (button) {
|
|
|
+ QString buttonText = button->text();
|
|
|
+ // 发送按钮上的数字作为消息
|
|
|
+ m_messageView->addMessage(buttonText, "", "我", false);
|
|
|
+ if (m_webSocketClient) {
|
|
|
+ m_webSocketClient->sendMessage(buttonText);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送消息后隐藏快捷按钮
|
|
|
+ hideQuickButtons();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void ChatWindow::onStreamNotification(bool show)
|
|
|
+{
|
|
|
+ if (show) {
|
|
|
+ showQuickButtons();
|
|
|
+ } else {
|
|
|
+ hideQuickButtons();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void ChatWindow::showQuickButtons()
|
|
|
+{
|
|
|
+ if (m_quickButtonContainer) {
|
|
|
+ m_quickButtonContainer->setVisible(true);
|
|
|
+ // 启动定时器,10秒后自动隐藏
|
|
|
+ m_hideTimer->start(10000);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void ChatWindow::hideQuickButtons()
|
|
|
+{
|
|
|
+ if (m_quickButtonContainer) {
|
|
|
+ m_quickButtonContainer->setVisible(false);
|
|
|
+ m_hideTimer->stop();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void ChatWindow::setQuickButtonsVisible(bool visible)
|
|
|
+{
|
|
|
+ if (m_quickButtonContainer) {
|
|
|
+ m_quickButtonContainer->setVisible(visible);
|
|
|
+ if (visible) {
|
|
|
+ // 如果显示,启动定时器
|
|
|
+ m_hideTimer->start(10000);
|
|
|
+ } else {
|
|
|
+ // 如果隐藏,停止定时器
|
|
|
+ m_hideTimer->stop();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
void ChatWindow::sendMessage()
|
|
|
{
|
|
|
QString text = m_inputEdit->getPlainText();
|
|
|
QStringList imagePaths = m_inputEdit->getImagePaths();
|
|
|
-
|
|
|
+
|
|
|
// 发送文本消息
|
|
|
if (!text.isEmpty()) {
|
|
|
m_messageView->addMessage(text, "", "我", false);
|
|
|
@@ -459,14 +566,17 @@ void ChatWindow::sendMessage()
|
|
|
m_webSocketClient->sendMessage(text);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 发送图片消息
|
|
|
for (const QString &imagePath : imagePaths) {
|
|
|
sendImageMessage(imagePath, text);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 清空输入框
|
|
|
m_inputEdit->clearContent();
|
|
|
+
|
|
|
+ // 发送任意消息后隐藏快捷按钮
|
|
|
+ hideQuickButtons();
|
|
|
}
|
|
|
|
|
|
void ChatWindow::sendImageMessage(const QString &imagePath, const QString &text)
|
|
|
@@ -489,3 +599,11 @@ void ChatWindow::closeEvent(QCloseEvent *event)
|
|
|
event->ignore();
|
|
|
emit windowCloseRequested();
|
|
|
}
|
|
|
+
|
|
|
+void ChatWindow::onStreamButtonClicked()
|
|
|
+{
|
|
|
+ // 发送推流通知,显示快捷按钮
|
|
|
+ if (m_webSocketClient) {
|
|
|
+ m_webSocketClient->sendStreamNotification(true);
|
|
|
+ }
|
|
|
+}
|