| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- #include "chatwindow.h"
- #include <QAction>
- #include <QApplication>
- #include <QButtonGroup>
- #include <QClipboard>
- #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 <QUuid>
- #include "appevent.h"
- #include "qpushbutton.h"
- #include "widgets/chatView/chat1/chatview.h"
- // MultiFormatInputEdit 实现
- MultiFormatInputEdit::MultiFormatInputEdit(QWidget *parent)
- : QTextEdit(parent)
- {
- setAcceptDrops(true);
- setPlaceholderText("输入消息... (支持粘贴图片)");
- setMaximumHeight(120);
- setMinimumHeight(60);
- // 连接文本变化信号
- connect(this, &QTextEdit::textChanged, this, &MultiFormatInputEdit::onTextChanged);
- }
- QString MultiFormatInputEdit::getPlainText() const
- {
- // 获取纯文本,但保留图片占位符信息
- QString text = toPlainText().trimmed();
- return text;
- }
- QStringList MultiFormatInputEdit::getImagePaths() const
- {
- return m_imagePaths;
- }
- 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();
- }
- void MultiFormatInputEdit::clearContent()
- {
- clear();
- m_imagePaths.clear();
- m_imageResourceMap.clear();
- document()->clear();
- emit contentChanged();
- }
- 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;
- }
- 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 MultiFormatInputEdit::dragEnterEvent(QDragEnterEvent *event)
- {
- if (event->mimeData()->hasImage() || event->mimeData()->hasUrls()) {
- event->acceptProposedAction();
- } else {
- QTextEdit::dragEnterEvent(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) {
- 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 {
- 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 MultiFormatInputEdit::insertFromMimeData(const QMimeData *source)
- {
- 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);
- }
- }
- 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;
- }
- void MultiFormatInputEdit::onTextChanged()
- {
- emit contentChanged();
- }
- 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);
- }
- }
- }
- bool MultiFormatInputEdit::isImageFormat(const QString &fileName) const
- {
- QStringList imageFormats = {"png", "jpg", "jpeg", "gif", "bmp", "tiff", "webp"};
- QString suffix = QFileInfo(fileName).suffix().toLower();
- return imageFormats.contains(suffix);
- }
- 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 MultiFormatInputEdit::insertImageIntoDocument(const QString &imagePath)
- {
- QPixmap pixmap(imagePath);
- if (pixmap.isNull())
- 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());
- }
- // ChatWindow 实现
- 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();
- }
- 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);
- // 按钮区域
- 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();
- }
- 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);
- connect(m_streamButton, &QPushButton::clicked, this, &ChatWindow::onStreamButtonClicked);
- if (AppEvent::instance()->hasRole("role.admin")) {
- m_streamButton->setVisible(true);
- } else {
- m_streamButton->setVisible(false);
- }
- // 快速按钮信号连接
- 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,
- [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("与服务器断开连接");
- }
- });
-
- // 连接推流通知信号
- connect(m_webSocketClient, &WebSocketClient::streamNotification, this, &ChatWindow::onStreamNotification);
- }
- }
- 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::onInputContentChanged()
- {
- // 更新发送按钮状态
- m_sendButton->setEnabled(m_inputEdit->validateContent());
- }
- void ChatWindow::onImageInserted(const QString &imagePath)
- {
- Q_UNUSED(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);
- if (m_webSocketClient) {
- 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)
- {
- 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)
- {
- event->ignore();
- emit windowCloseRequested();
- }
- void ChatWindow::onStreamButtonClicked()
- {
- // 发送推流通知,显示快捷按钮
- if (m_webSocketClient) {
- m_webSocketClient->sendStreamNotification(true);
- }
- }
|