| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933 |
- #include "BubbleMessageManager.h"
- #include <algorithm>
- #include <QAbstractTextDocumentLayout>
- #include <QFileInfo>
- #include <QGuiApplication>
- #include <QImageReader>
- #include <QScreen>
- #include <QScrollBar>
- #include <QTextBlock>
- #include <QTextCursor>
- #include <QTextDocumentFragment>
- // 基础气泡消息实现
- 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<class TextMessage *>(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<BubbleMessage *>(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<QGraphicsItem *> 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<BubbleMessage *>(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;
- }
- }
|