#include "BubbleMessageManager.h" #include #include #include #include #include #include #include #include #include #include // 基础气泡消息实现 BubbleMessage::BubbleMessage(const QString &content, MessageType type, MessageDirection direction, QGraphicsItem *parent) : QGraphicsItem(parent) , m_type(type) , m_direction(direction) , m_content(content) , m_timestamp(QDateTime::currentDateTime()) , m_isHovered(false) , m_padding(10) , m_borderRadius(8) , m_avatarSize(40) , m_isSelecting(false) { setAcceptHoverEvents(true); // 根据消息方向设置不同的颜色 if (direction == Sent) { m_bubbleColor = QColor(0, 145, 255); m_textColor = Qt::white; } else { m_bubbleColor = QColor(240, 240, 240); m_textColor = Qt::black; } m_font = QFont("Microsoft YaHei", 10); updateSize(); } BubbleMessage::~BubbleMessage() {} QRectF BubbleMessage::boundingRect() const { // 时间消息和撤回消息不需要考虑头像 if (m_type == TimeMessage || m_type == RecallMessage) { return m_boundingRect; } else { // 为头像预留空间,但不改变气泡本身的位置 QRectF rect = m_boundingRect; if (m_direction == Sent) { // 发送消息:头像在右侧,扩展boundingRect但不移动气泡 rect.setWidth(rect.width() + m_avatarSize + 10); } else { // 接收消息:头像在左侧,扩展boundingRect并向左移动 rect.setX(-m_avatarSize - 10); rect.setWidth(rect.width() + m_avatarSize + 10); } return rect; } } void BubbleMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); Q_UNUSED(option); Q_UNUSED(widget); painter->setRenderHint(QPainter::Antialiasing); // 绘制气泡背景 QPainterPath path; path.addRoundedRect(m_boundingRect, m_borderRadius, m_borderRadius); // 添加小三角形指向头像 if (m_type != TimeMessage && m_type != RecallMessage && !m_avatar.isNull()) { if (m_direction == Sent) { // 发送消息:右侧小三角 QPointF triangleTop(m_boundingRect.right(), m_boundingRect.top() + 20); QPointF triangleBottom(m_boundingRect.right(), m_boundingRect.top() + 40); QPointF trianglePoint(m_boundingRect.right() + 10, m_boundingRect.top() + 30); QPainterPath trianglePath; trianglePath.moveTo(triangleTop); trianglePath.lineTo(trianglePoint); trianglePath.lineTo(triangleBottom); trianglePath.lineTo(triangleTop); path.addPath(trianglePath); } else { // 接收消息:左侧小三角 QPointF triangleTop(m_boundingRect.left(), m_boundingRect.top() + 20); QPointF triangleBottom(m_boundingRect.left(), m_boundingRect.top() + 40); QPointF trianglePoint(m_boundingRect.left() - 10, m_boundingRect.top() + 30); QPainterPath trianglePath; trianglePath.moveTo(triangleTop); trianglePath.lineTo(trianglePoint); trianglePath.lineTo(triangleBottom); trianglePath.lineTo(triangleTop); path.addPath(trianglePath); } } painter->fillPath(path, m_bubbleColor); // 如果鼠标悬停,绘制边框 if (m_isHovered && isSelectable()) { QPen pen(Qt::gray, 1, Qt::DashLine); painter->setPen(pen); painter->drawPath(path); } // 绘制头像(除了时间消息和撤回消息) if (m_type != TimeMessage && m_type != RecallMessage && !m_avatar.isNull()) { QRectF avatarRect; if (m_direction == Sent) { // 发送消息:头像在气泡右侧 avatarRect = QRectF(m_boundingRect.right() + 10, 0, m_avatarSize, m_avatarSize); } else { // 接收消息:头像在气泡左侧 avatarRect = QRectF(-m_avatarSize - 10, 0, m_avatarSize, m_avatarSize); } // 绘制圆形头像 QPainterPath avatarPath; avatarPath.addEllipse(avatarRect); painter->setClipPath(avatarPath); painter->drawPixmap(avatarRect, m_avatar, m_avatar.rect()); painter->setClipping(false); } } void BubbleMessage::updateSize() { // 默认大小,子类会重写此方法 m_boundingRect = QRectF(0, 0, 200, 50); } void BubbleMessage::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { if (!isSelectable()) { QGraphicsItem::contextMenuEvent(event); return; } QMenu menu; QAction *copyAction = menu.addAction("复制全部"); class TextMessage *textMsg = dynamic_cast(this); QAction *copySelectedAction = nullptr; if (textMsg && textMsg->m_hasSelection && textMsg->m_selectionStart != textMsg->m_selectionEnd) { copySelectedAction = menu.addAction("复制选中文本"); } QAction *selectedAction = menu.exec(event->screenPos()); if (selectedAction == copyAction) { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(m_content); } else if (selectedAction == copySelectedAction) { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(textMsg->selectedText()); } event->accept(); } void BubbleMessage::mousePressEvent(QGraphicsSceneMouseEvent *event) { QGraphicsItem::mousePressEvent(event); } void BubbleMessage::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { QGraphicsItem::mouseReleaseEvent(event); } void BubbleMessage::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { m_isHovered = true; update(); QGraphicsItem::hoverEnterEvent(event); } void BubbleMessage::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { m_isHovered = false; update(); QGraphicsItem::hoverLeaveEvent(event); } // 时间消息实现 TimeMessage::TimeMessage(const QDateTime &time, QGraphicsItem *parent) : BubbleMessage(time.toString("yyyy-MM-dd hh:mm:ss"), MessageType::TimeMessage, Received, parent) { m_bubbleColor = QColor(230, 230, 230, 150); m_textColor = QColor(100, 100, 100); updateSize(); } void TimeMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { BubbleMessage::paint(painter, option, widget); painter->setFont(m_font); painter->setPen(m_textColor); painter->drawText(m_boundingRect, Qt::AlignCenter, m_content); } // 文本消息实现 TextMessage::TextMessage(const QString &text, MessageDirection direction, QGraphicsItem *parent) : BubbleMessage(text, MessageType::TextMessage, direction, parent) { m_textDocument.setDefaultFont(m_font); m_textDocument.setDocumentMargin(m_padding); m_textDocument.setHtml(text); // 确保接收鼠标事件 setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); setFlag(QGraphicsItem::ItemIsSelectable, true); updateSize(); } void TextMessage::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::LeftButton) { QPointF pos = event->pos(); if (m_boundingRect.contains(pos)) { m_isSelecting = true; m_selectionStart = positionAt(pos - QPointF(m_padding, m_padding)); m_selectionEnd = m_selectionStart; m_hasSelection = false; update(); } } BubbleMessage::mousePressEvent(event); } void TextMessage::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (m_isSelecting) { QPointF pos = event->pos(); m_selectionEnd = positionAt(pos - QPointF(m_padding, m_padding)); m_hasSelection = true; update(); } BubbleMessage::mouseMoveEvent(event); } void TextMessage::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (m_isSelecting) { m_isSelecting = false; if (m_hasSelection && m_selectionStart != m_selectionEnd) { // 如果有选中文本,可以在这里显示复制菜单 QMenu menu; QAction *copyAction = menu.addAction("复制选中文本"); QAction *selectedAction = menu.exec(event->screenPos()); if (selectedAction == copyAction) { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(selectedText()); } } } BubbleMessage::mouseReleaseEvent(event); } // 获取指定位置的文本位置 int TextMessage::positionAt(const QPointF &pos) const { return m_textDocument.documentLayout()->hitTest(pos, Qt::FuzzyHit); } // 获取选中的文本 QString TextMessage::selectedText() { if (!m_hasSelection || m_selectionStart == m_selectionEnd) return QString(); QTextCursor cursor(&m_textDocument); int start = qMin(m_selectionStart, m_selectionEnd); int end = qMax(m_selectionStart, m_selectionEnd); cursor.setPosition(start); cursor.setPosition(end, QTextCursor::KeepAnchor); return cursor.selectedText(); } void TextMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { BubbleMessage::paint(painter, option, widget); painter->setFont(m_font); painter->setPen(m_textColor); // 保存画家状态 painter->save(); painter->translate(m_padding, m_padding); // 设置文本颜色 QAbstractTextDocumentLayout::PaintContext context; context.palette.setColor(QPalette::Text, m_textColor); // 如果有选中区域,设置选中区域的格式 if (m_hasSelection && m_selectionStart != m_selectionEnd) { QTextCursor cursor(&m_textDocument); int start = qMin(m_selectionStart, m_selectionEnd); int end = qMax(m_selectionStart, m_selectionEnd); cursor.setPosition(start); cursor.setPosition(end, QTextCursor::KeepAnchor); QAbstractTextDocumentLayout::Selection selection; selection.cursor = cursor; selection.format.setBackground(Qt::lightGray); selection.format.setForeground(Qt::black); context.selections.append(selection); } // 绘制文本 m_textDocument.documentLayout()->draw(painter, context); // 恢复画家状态 painter->restore(); } void TextMessage::updateSize() { // 使用视图宽度而不是屏幕宽度 int viewWidth = 500; // 默认值 // 尝试获取实际视图宽度 if (scene() && !scene()->views().isEmpty()) { viewWidth = scene()->views().first()->width(); } // 使用视图宽度的60%,并减去边距和头像空间 int maxWidth = viewWidth * 0.6 - m_avatarSize - 30; // 30为额外边距 m_textDocument.setTextWidth(maxWidth - 2 * m_padding); QSizeF docSize = m_textDocument.size(); m_boundingRect = QRectF(0, 0, docSize.width() + 2 * m_padding, docSize.height() + 2 * m_padding); } // 成功消息实现 SuccessMessage::SuccessMessage(const QString &text, MessageDirection direction, QGraphicsItem *parent) : TextMessage(text, direction, parent) { m_type = MessageType::SuccessMessage; m_bubbleColor = QColor(76, 175, 80); m_textColor = Qt::white; } void SuccessMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { TextMessage::paint(painter, option, widget); // 绘制成功图标 painter->setPen(Qt::white); painter->setBrush(Qt::white); QRectF iconRect(m_boundingRect.right() - 25, m_boundingRect.top() + 5, 20, 20); painter->drawEllipse(iconRect.center(), 8, 8); QPainterPath checkPath; checkPath.moveTo(iconRect.center().x() - 4, iconRect.center().y()); checkPath.lineTo(iconRect.center().x(), iconRect.center().y() + 4); checkPath.lineTo(iconRect.center().x() + 4, iconRect.center().y() - 4); QPen checkPen(Qt::green, 2); painter->setPen(checkPen); painter->drawPath(checkPath); } // 失败消息实现 FailureMessage::FailureMessage(const QString &text, MessageDirection direction, QGraphicsItem *parent) : TextMessage(text, direction, parent) { m_type = MessageType::FailureMessage; m_bubbleColor = QColor(244, 67, 54); m_textColor = Qt::white; } void FailureMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { TextMessage::paint(painter, option, widget); // 绘制失败图标 painter->setPen(Qt::white); painter->setBrush(Qt::white); QRectF iconRect(m_boundingRect.right() - 25, m_boundingRect.top() + 5, 20, 20); painter->drawEllipse(iconRect.center(), 8, 8); QPen crossPen(Qt::red, 2); painter->setPen(crossPen); painter->drawLine(iconRect.center().x() - 4, iconRect.center().y() - 4, iconRect.center().x() + 4, iconRect.center().y() + 4); painter->drawLine(iconRect.center().x() - 4, iconRect.center().y() + 4, iconRect.center().x() + 4, iconRect.center().y() - 4); } // 文件消息实现 FileMessage::FileMessage(const QString &fileName, const QString &filePath, qint64 fileSize, MessageDirection direction, QGraphicsItem *parent) : BubbleMessage(fileName, MessageType::FileMessage, direction, parent) , m_fileName(fileName) , m_filePath(filePath) , m_fileSize(fileSize) { // 根据文件类型获取图标 QFileInfo fileInfo(filePath); QString suffix = fileInfo.suffix().toLower(); // 这里可以根据不同的文件类型设置不同的图标 if (suffix == "pdf") { m_fileIcon = QPixmap(":/icons/pdf.png"); } else if (suffix == "doc" || suffix == "docx") { m_fileIcon = QPixmap(":/icons/word.png"); } else if (suffix == "xls" || suffix == "xlsx") { m_fileIcon = QPixmap(":/icons/excel.png"); } else if (suffix == "ppt" || suffix == "pptx") { m_fileIcon = QPixmap(":/icons/powerpoint.png"); } else if (suffix == "zip" || suffix == "rar" || suffix == "7z") { m_fileIcon = QPixmap(":/icons/archive.png"); } else { m_fileIcon = QPixmap(":/icons/file.png"); } // 如果没有找到图标资源,使用默认图标 if (m_fileIcon.isNull()) { m_fileIcon = QPixmap(32, 32); m_fileIcon.fill(Qt::transparent); QPainter p(&m_fileIcon); p.setPen(QPen(Qt::gray, 1)); p.setBrush(Qt::white); p.drawRect(0, 0, 31, 31); p.drawText(QRect(0, 0, 32, 32), Qt::AlignCenter, "FILE"); } updateSize(); } void FileMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { BubbleMessage::paint(painter, option, widget); // 绘制文件图标 QRectF iconRect(m_padding, m_padding, 32, 32); painter->drawPixmap(iconRect, m_fileIcon, m_fileIcon.rect()); // 绘制文件名 painter->setPen(m_textColor); painter->setFont(QFont(m_font.family(), m_font.pointSize(), QFont::Bold)); QRectF nameRect(iconRect.right() + 5, m_padding, m_boundingRect.width() - iconRect.right() - 10, 20); painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, m_fileName); // 绘制文件大小 painter->setFont(QFont(m_font.family(), m_font.pointSize() - 1)); QRectF sizeRect(iconRect.right() + 5, nameRect.bottom(), nameRect.width(), 20); // 格式化文件大小 QString sizeStr; if (m_fileSize < 1024) { sizeStr = QString("%1 B").arg(m_fileSize); } else if (m_fileSize < 1024 * 1024) { sizeStr = QString("%1 KB").arg(m_fileSize / 1024.0, 0, 'f', 2); } else if (m_fileSize < 1024 * 1024 * 1024) { sizeStr = QString("%1 MB").arg(m_fileSize / (1024.0 * 1024.0), 0, 'f', 2); } else { sizeStr = QString("%1 GB").arg(m_fileSize / (1024.0 * 1024.0 * 1024.0), 0, 'f', 2); } painter->drawText(sizeRect, Qt::AlignLeft | Qt::AlignVCenter, sizeStr); } void FileMessage::updateSize() { // 文件消息的固定大小 m_boundingRect = QRectF(0, 0, 250, 60); } // 撤回消息实现 RecallMessage::RecallMessage(const QString &recalledBy, MessageDirection direction, QGraphicsItem *parent) : BubbleMessage(QString("%1 撤回了一条消息").arg(recalledBy), MessageType::RecallMessage, direction, parent) { m_bubbleColor = QColor(200, 200, 200, 150); m_textColor = QColor(100, 100, 100); updateSize(); } void RecallMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { BubbleMessage::paint(painter, option, widget); painter->setFont(m_font); painter->setPen(m_textColor); painter->drawText(m_boundingRect.adjusted(m_padding, m_padding, -m_padding, -m_padding), Qt::AlignCenter, m_content); } // 图片消息实现 ImageMessage::ImageMessage(const QPixmap &image, MessageDirection direction, QGraphicsItem *parent) : BubbleMessage("", MessageType::ImageMessage, direction, parent) , m_image(image) { updateSize(); } void ImageMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { BubbleMessage::paint(painter, option, widget); // 绘制图片 QRectF imageRect = m_boundingRect.adjusted(m_padding, m_padding, -m_padding, -m_padding); painter->drawPixmap(imageRect, m_scaledImage, m_scaledImage.rect()); } void ImageMessage::updateSize() { // 限制最大尺寸 int maxWidth = QGuiApplication::primaryScreen()->size().width() * 0.4; int maxHeight = 300; if (m_image.width() > maxWidth || m_image.height() > maxHeight) { m_scaledImage = m_image.scaled(maxWidth, maxHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { m_scaledImage = m_image; } m_boundingRect = QRectF(0, 0, m_scaledImage.width() + 2 * m_padding, m_scaledImage.height() + 2 * m_padding); } // 文本图片混合消息实现 TextImageMessage::TextImageMessage(const QString &text, const QPixmap &image, MessageDirection direction, QGraphicsItem *parent) : BubbleMessage(text, MessageType::TextImageMessage, direction, parent) , m_image(image) { m_textDocument.setDefaultFont(m_font); m_textDocument.setDocumentMargin(m_padding); m_textDocument.setHtml(text); updateSize(); } void TextImageMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { BubbleMessage::paint(painter, option, widget); // 保存画家状态 painter->save(); // 绘制文本 painter->setPen(m_textColor); painter->translate(m_padding, m_padding); QAbstractTextDocumentLayout::PaintContext context; context.palette.setColor(QPalette::Text, m_textColor); m_textDocument.documentLayout()->draw(painter, context); // 恢复画家状态 painter->restore(); // 绘制图片 QSizeF docSize = m_textDocument.size(); QRectF imageRect(m_padding, m_padding + docSize.height() + 5, m_scaledImage.width(), m_scaledImage.height()); painter->drawPixmap(imageRect, m_scaledImage, m_scaledImage.rect()); } void TextImageMessage::updateSize() { // 限制最大宽度 int maxWidth = QGuiApplication::primaryScreen()->size().width() * 0.4; m_textDocument.setTextWidth(maxWidth - 2 * m_padding); // 限制图片最大尺寸 int maxImageWidth = maxWidth - 2 * m_padding; int maxImageHeight = 200; if (m_image.width() > maxImageWidth || m_image.height() > maxImageHeight) { m_scaledImage = m_image.scaled(maxImageWidth, maxImageHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { m_scaledImage = m_image; } QSizeF docSize = m_textDocument.size(); m_boundingRect = QRectF(0, 0, std::max((int) docSize.width() + 2 * m_padding, m_scaledImage.width() + 2 * m_padding), docSize.height() + m_scaledImage.height() + 2 * m_padding + 5); } // 消息场景实现 MessageScene::MessageScene(QObject *parent) : QGraphicsScene(parent) , m_verticalSpacing(10) , m_horizontalMargin(20) { setBackgroundBrush(QColor(245, 245, 245)); // 创建默认头像 m_defaultSentAvatar = QPixmap(40, 40); m_defaultSentAvatar.fill(Qt::blue); m_defaultReceivedAvatar = QPixmap(40, 40); m_defaultReceivedAvatar.fill(Qt::green); } void MessageScene::addMessage(BubbleMessage *message) { if (message->messageType() != BubbleMessage::TimeMessage && message->messageType() != BubbleMessage::RecallMessage) { if (message->messageDirection() == BubbleMessage::Sent) { message->setAvatar(m_defaultSentAvatar); } else { message->setAvatar(m_defaultReceivedAvatar); } } // 存储消息时间戳用于排序 message->setData(0, message->timestamp()); addItem(message); // 直接放置新消息,而不是重新排列所有消息 placeNewMessage(message); // 确保视图滚动到最新消息 if (!views().isEmpty() && views().first()->verticalScrollBar()) { views().first()->verticalScrollBar()->setValue( views().first()->verticalScrollBar()->maximum()); } } void MessageScene::placeNewMessage(BubbleMessage *message) { qreal sceneWidth = views().isEmpty() ? 500 : views().first()->width(); // 获取当前场景中最后一条消息的位置 qreal currentY = 10; // 默认起始位置 if (!items().isEmpty()) { // 找到当前最底部的消息 qreal maxY = 0; for (QGraphicsItem *item : items()) { BubbleMessage *msg = dynamic_cast(item); if (msg) { qreal itemBottom = msg->y() + msg->boundingRect().height(); if (itemBottom > maxY) { maxY = itemBottom; } } } currentY = maxY + m_verticalSpacing; } // 计算消息的X坐标 qreal x; if (message->messageType() == BubbleMessage::TimeMessage || message->messageType() == BubbleMessage::RecallMessage) { // 时间消息和撤回消息居中显示 x = (sceneWidth - message->boundingRect().width()) / 2; } else if (message->messageDirection() == BubbleMessage::Sent) { // 发送的消息靠右显示,但要留出头像的空间 x = sceneWidth - message->boundingRect().width() - m_horizontalMargin; } else { // 接收的消息靠左显示,考虑头像空间 x = m_horizontalMargin + message->avatar().width() + 10; } // 设置消息位置 message->setPos(x, currentY); // 更新场景高度,确保能看到所有消息 QRectF currentRect = sceneRect(); qreal newHeight = currentY + message->boundingRect().height() + m_verticalSpacing; if (newHeight > currentRect.height()) { setSceneRect(0, 0, sceneWidth, newHeight); } } void MessageScene::clear() { QGraphicsScene::clear(); } void MessageScene::setDefaultAvatars(const QPixmap &sentAvatar, const QPixmap &receivedAvatar) { m_defaultSentAvatar = sentAvatar; m_defaultReceivedAvatar = receivedAvatar; } void MessageScene::arrangeMessages() { // 获取所有消息项 QList items = this->items(); // 按照时间戳排序(从早到晚) std::sort(items.begin(), items.end(), [](QGraphicsItem *a, QGraphicsItem *b) { return a->data(0).toDateTime() < b->data(0).toDateTime(); }); qreal currentY = 10; qreal sceneWidth = views().isEmpty() ? 500 : views().first()->width(); for (QGraphicsItem *item : items) { BubbleMessage *message = dynamic_cast(item); if (!message) continue; qreal x; if (message->messageType() == BubbleMessage::TimeMessage || message->messageType() == BubbleMessage::RecallMessage) { // 时间消息和撤回消息居中显示 x = (sceneWidth - message->boundingRect().width()) / 2; } else if (message->messageDirection() == BubbleMessage::Sent) { // 发送的消息靠右显示,但要留出头像的空间 x = sceneWidth - message->boundingRect().width() - m_horizontalMargin; } else { // 接收的消息靠左显示,考虑头像空间 x = m_horizontalMargin + message->avatar().width() + 10; } message->setPos(x, currentY); currentY += message->boundingRect().height() + m_verticalSpacing; } // 更新场景矩形 setSceneRect(0, 0, sceneWidth, currentY); } // 消息视图实现 BubbleMessageView::BubbleMessageView(QWidget *parent) : QGraphicsView(parent) , m_autoScroll(true) { m_scene = new MessageScene(this); setScene(m_scene); // 设置视图属性 setRenderHint(QPainter::Antialiasing); setRenderHint(QPainter::SmoothPixmapTransform); setRenderHint(QPainter::TextAntialiasing); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); // 设置视图背景 setBackgroundBrush(QColor(245, 245, 245)); // 设置拖拽模式 setDragMode(QGraphicsView::NoDrag); // 优化性能 setViewportUpdateMode(QGraphicsView::SmartViewportUpdate); setCacheMode(QGraphicsView::CacheBackground); setOptimizationFlags(QGraphicsView::DontAdjustForAntialiasing | QGraphicsView::DontSavePainterState); } void BubbleMessageView::addTimeMessage(const QDateTime &time) { class TimeMessage *message = new class TimeMessage(time); m_scene->addMessage(message); if (m_autoScroll) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } } void BubbleMessageView::addTextMessage(const QString &text, BubbleMessage::MessageDirection direction) { class TextMessage *message = new class TextMessage(text, direction); m_scene->addMessage(message); // if (m_autoScroll) { // verticalScrollBar()->setValue(verticalScrollBar()->maximum()); // } } void BubbleMessageView::addSuccessMessage(const QString &text, BubbleMessage::MessageDirection direction) { class SuccessMessage *message = new class SuccessMessage(text, direction); m_scene->addMessage(message); if (m_autoScroll) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } } void BubbleMessageView::addFailureMessage(const QString &text, BubbleMessage::MessageDirection direction) { class FailureMessage *message = new class FailureMessage(text, direction); m_scene->addMessage(message); if (m_autoScroll) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } } void BubbleMessageView::addFileMessage(const QString &fileName, const QString &filePath, qint64 fileSize, BubbleMessage::MessageDirection direction) { class FileMessage *message = new class FileMessage(fileName, filePath, fileSize, direction); m_scene->addMessage(message); if (m_autoScroll) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } } void BubbleMessageView::addRecallMessage(const QString &recalledBy, BubbleMessage::MessageDirection direction) { class RecallMessage *message = new class RecallMessage(recalledBy, direction); m_scene->addMessage(message); if (m_autoScroll) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } } void BubbleMessageView::addImageMessage(const QPixmap &image, BubbleMessage::MessageDirection direction) { class ImageMessage *message = new class ImageMessage(image, direction); m_scene->addMessage(message); if (m_autoScroll) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } } void BubbleMessageView::addTextImageMessage(const QString &text, const QPixmap &image, BubbleMessage::MessageDirection direction) { class TextImageMessage *message = new class TextImageMessage(text, image, direction); m_scene->addMessage(message); if (m_autoScroll) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } } void BubbleMessageView::clearMessages() { m_scene->clear(); } void BubbleMessageView::resizeEvent(QResizeEvent *event) { QGraphicsView::resizeEvent(event); // 调整场景大小以适应视图 QRectF sceneRect = m_scene->sceneRect(); sceneRect.setWidth(viewport()->width()); m_scene->setSceneRect(sceneRect); // 重新排列消息 m_scene->arrangeMessages(); } void BubbleMessageView::wheelEvent(QWheelEvent *event) { // 检测用户是否手动滚动 int maxValue = verticalScrollBar()->maximum(); int currentValue = verticalScrollBar()->value(); QGraphicsView::wheelEvent(event); // 如果用户向上滚动,禁用自动滚动 if (event->angleDelta().y() > 0 && currentValue < maxValue) { m_autoScroll = false; } // 如果用户滚动到底部,启用自动滚动 if (verticalScrollBar()->value() >= verticalScrollBar()->maximum()) { m_autoScroll = true; } }