zhuizhu 6 сар өмнө
parent
commit
c065fcc9bf

+ 29 - 0
network/websocketclient.cpp

@@ -176,6 +176,26 @@ void WebSocketClient::sendImageMessage(const QString& imagePath, const QString&
     m_webSocket.sendTextMessage(doc.toJson(QJsonDocument::Compact));
 }
 
+void WebSocketClient::sendStreamNotification(bool show)
+{
+    if (!m_connected) {
+        emit errorOccurred("未连接到聊天室,无法发送推流通知");
+        return;
+    }
+
+    // 使用现有的room类型,在content中添加特殊标识
+    QJsonObject message;
+    message["type"] = "room";
+    message["content"] = show ? "##STREAM_SHOW##" : "##STREAM_HIDE##";
+    message["roomId"] = m_roomId;
+    message["time"] = QDateTime::currentDateTime().toUTC().toString(Qt::ISODate);
+
+    QJsonDocument doc(message);
+
+    qDebug() << doc.toJson(QJsonDocument::Compact);
+    m_webSocket.sendTextMessage(doc.toJson(QJsonDocument::Compact));
+}
+
 bool WebSocketClient::isConnected() const
 {
     return m_connected;
@@ -242,6 +262,15 @@ void WebSocketClient::onTextMessageReceived(const QString& message)
 
         // 设置消息类型
         if (type == "room") {
+            // 检查是否为推流通知消息
+            if (content == "##STREAM_SHOW##") {
+                emit streamNotification(true);
+                return;
+            } else if (content == "##STREAM_HIDE##") {
+                emit streamNotification(false);
+                return;
+            }
+            
             chatMessage.type = MessageType::Left;
         } else if (type == "system") {
             chatMessage.type = MessageType::System;

+ 5 - 0
network/websocketclient.h

@@ -27,6 +27,9 @@ public:
     
     // 发送图片消息
     void sendImageMessage(const QString& imagePath, const QString& text = QString());
+    
+    // 发送推流通知消息
+    void sendStreamNotification(bool show);
 
     // 获取连接状态
     bool isConnected() const;
@@ -46,6 +49,8 @@ signals:
     void liveStatus(const QString& message);
     // 统计信息更新
     void statsUpdate(const QJsonObject& statsData);
+    // 推流通知信号
+    void streamNotification(bool show);
 
     // 连接状态变化信号
     void connectionStateChanged(bool connected);

+ 201 - 83
widgets/chatView/chatwindow.cpp

@@ -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);
+    }
+}

+ 11 - 0
widgets/chatView/chatwindow.h

@@ -18,6 +18,7 @@
 #include <QPixmap>
 #include <QContextMenuEvent>
 #include <QMap>
+#include <QTimer>
 #include "network/websocketclient.h"
 
 class ChatView;
@@ -95,6 +96,10 @@ private slots:
     void onRecallClicked();
     void onInputContentChanged();
     void onImageInserted(const QString &imagePath);
+    void onQuickButtonClicked();
+    void onStreamNotification(bool show);
+    void hideQuickButtons();
+    void onStreamButtonClicked();
 
 private:
     ChatView *m_messageView;
@@ -102,6 +107,10 @@ private:
     QPushButton *m_sendButton;
     QPushButton *m_imageButton;
     QPushButton *m_fileButton;
+    QPushButton *m_streamButton;
+    QButtonGroup *m_quickButtonGroup;
+    QWidget *m_quickButtonContainer;
+    QTimer *m_hideTimer;
 
     QString m_roomId;
     WebSocketClient *m_webSocketClient;
@@ -112,6 +121,8 @@ private:
     void sendMessage();
     void sendImageMessage(const QString &imagePath, const QString &text = QString());
     bool validateInput() const;
+    void showQuickButtons();
+    void setQuickButtonsVisible(bool visible);
 };
 
 #endif // CHATWINDOW_H