bubblemessagemanager.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  1. #include "BubbleMessageManager.h"
  2. #include <algorithm>
  3. #include <QAbstractTextDocumentLayout>
  4. #include <QFileInfo>
  5. #include <QGuiApplication>
  6. #include <QImageReader>
  7. #include <QScreen>
  8. #include <QScrollBar>
  9. #include <QTextBlock>
  10. #include <QTextCursor>
  11. #include <QTextDocumentFragment>
  12. // 基础气泡消息实现
  13. BubbleMessage::BubbleMessage(const QString &content,
  14. MessageType type,
  15. MessageDirection direction,
  16. QGraphicsItem *parent)
  17. : QGraphicsItem(parent)
  18. , m_type(type)
  19. , m_direction(direction)
  20. , m_content(content)
  21. , m_timestamp(QDateTime::currentDateTime())
  22. , m_isHovered(false)
  23. , m_padding(10)
  24. , m_borderRadius(8)
  25. , m_avatarSize(40)
  26. , m_isSelecting(false)
  27. {
  28. setAcceptHoverEvents(true);
  29. // 根据消息方向设置不同的颜色
  30. if (direction == Sent) {
  31. m_bubbleColor = QColor(0, 145, 255);
  32. m_textColor = Qt::white;
  33. } else {
  34. m_bubbleColor = QColor(240, 240, 240);
  35. m_textColor = Qt::black;
  36. }
  37. m_font = QFont("Microsoft YaHei", 10);
  38. updateSize();
  39. }
  40. BubbleMessage::~BubbleMessage() {}
  41. QRectF BubbleMessage::boundingRect() const
  42. {
  43. // 时间消息和撤回消息不需要考虑头像
  44. if (m_type == TimeMessage || m_type == RecallMessage) {
  45. return m_boundingRect;
  46. } else {
  47. // 为头像预留空间,但不改变气泡本身的位置
  48. QRectF rect = m_boundingRect;
  49. if (m_direction == Sent) {
  50. // 发送消息:头像在右侧,扩展boundingRect但不移动气泡
  51. rect.setWidth(rect.width() + m_avatarSize + 10);
  52. } else {
  53. // 接收消息:头像在左侧,扩展boundingRect并向左移动
  54. rect.setX(-m_avatarSize - 10);
  55. rect.setWidth(rect.width() + m_avatarSize + 10);
  56. }
  57. return rect;
  58. }
  59. }
  60. void BubbleMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  61. {
  62. Q_UNUSED(option);
  63. Q_UNUSED(widget);
  64. Q_UNUSED(option);
  65. Q_UNUSED(widget);
  66. painter->setRenderHint(QPainter::Antialiasing);
  67. // 绘制气泡背景
  68. QPainterPath path;
  69. path.addRoundedRect(m_boundingRect, m_borderRadius, m_borderRadius);
  70. // 添加小三角形指向头像
  71. if (m_type != TimeMessage && m_type != RecallMessage && !m_avatar.isNull()) {
  72. if (m_direction == Sent) {
  73. // 发送消息:右侧小三角
  74. QPointF triangleTop(m_boundingRect.right(), m_boundingRect.top() + 20);
  75. QPointF triangleBottom(m_boundingRect.right(), m_boundingRect.top() + 40);
  76. QPointF trianglePoint(m_boundingRect.right() + 10, m_boundingRect.top() + 30);
  77. QPainterPath trianglePath;
  78. trianglePath.moveTo(triangleTop);
  79. trianglePath.lineTo(trianglePoint);
  80. trianglePath.lineTo(triangleBottom);
  81. trianglePath.lineTo(triangleTop);
  82. path.addPath(trianglePath);
  83. } else {
  84. // 接收消息:左侧小三角
  85. QPointF triangleTop(m_boundingRect.left(), m_boundingRect.top() + 20);
  86. QPointF triangleBottom(m_boundingRect.left(), m_boundingRect.top() + 40);
  87. QPointF trianglePoint(m_boundingRect.left() - 10, m_boundingRect.top() + 30);
  88. QPainterPath trianglePath;
  89. trianglePath.moveTo(triangleTop);
  90. trianglePath.lineTo(trianglePoint);
  91. trianglePath.lineTo(triangleBottom);
  92. trianglePath.lineTo(triangleTop);
  93. path.addPath(trianglePath);
  94. }
  95. }
  96. painter->fillPath(path, m_bubbleColor);
  97. // 如果鼠标悬停,绘制边框
  98. if (m_isHovered && isSelectable()) {
  99. QPen pen(Qt::gray, 1, Qt::DashLine);
  100. painter->setPen(pen);
  101. painter->drawPath(path);
  102. }
  103. // 绘制头像(除了时间消息和撤回消息)
  104. if (m_type != TimeMessage && m_type != RecallMessage && !m_avatar.isNull()) {
  105. QRectF avatarRect;
  106. if (m_direction == Sent) {
  107. // 发送消息:头像在气泡右侧
  108. avatarRect = QRectF(m_boundingRect.right() + 10, 0, m_avatarSize, m_avatarSize);
  109. } else {
  110. // 接收消息:头像在气泡左侧
  111. avatarRect = QRectF(-m_avatarSize - 10, 0, m_avatarSize, m_avatarSize);
  112. }
  113. // 绘制圆形头像
  114. QPainterPath avatarPath;
  115. avatarPath.addEllipse(avatarRect);
  116. painter->setClipPath(avatarPath);
  117. painter->drawPixmap(avatarRect, m_avatar, m_avatar.rect());
  118. painter->setClipping(false);
  119. }
  120. }
  121. void BubbleMessage::updateSize()
  122. {
  123. // 默认大小,子类会重写此方法
  124. m_boundingRect = QRectF(0, 0, 200, 50);
  125. }
  126. void BubbleMessage::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
  127. {
  128. if (!isSelectable()) {
  129. QGraphicsItem::contextMenuEvent(event);
  130. return;
  131. }
  132. QMenu menu;
  133. QAction *copyAction = menu.addAction("复制全部");
  134. class TextMessage *textMsg = dynamic_cast<class TextMessage *>(this);
  135. QAction *copySelectedAction = nullptr;
  136. if (textMsg && textMsg->m_hasSelection && textMsg->m_selectionStart != textMsg->m_selectionEnd) {
  137. copySelectedAction = menu.addAction("复制选中文本");
  138. }
  139. QAction *selectedAction = menu.exec(event->screenPos());
  140. if (selectedAction == copyAction) {
  141. QClipboard *clipboard = QApplication::clipboard();
  142. clipboard->setText(m_content);
  143. } else if (selectedAction == copySelectedAction) {
  144. QClipboard *clipboard = QApplication::clipboard();
  145. clipboard->setText(textMsg->selectedText());
  146. }
  147. event->accept();
  148. }
  149. void BubbleMessage::mousePressEvent(QGraphicsSceneMouseEvent *event)
  150. {
  151. QGraphicsItem::mousePressEvent(event);
  152. }
  153. void BubbleMessage::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
  154. {
  155. QGraphicsItem::mouseReleaseEvent(event);
  156. }
  157. void BubbleMessage::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
  158. {
  159. m_isHovered = true;
  160. update();
  161. QGraphicsItem::hoverEnterEvent(event);
  162. }
  163. void BubbleMessage::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
  164. {
  165. m_isHovered = false;
  166. update();
  167. QGraphicsItem::hoverLeaveEvent(event);
  168. }
  169. // 时间消息实现
  170. TimeMessage::TimeMessage(const QDateTime &time, QGraphicsItem *parent)
  171. : BubbleMessage(time.toString("yyyy-MM-dd hh:mm:ss"), MessageType::TimeMessage, Received, parent)
  172. {
  173. m_bubbleColor = QColor(230, 230, 230, 150);
  174. m_textColor = QColor(100, 100, 100);
  175. updateSize();
  176. }
  177. void TimeMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  178. {
  179. BubbleMessage::paint(painter, option, widget);
  180. painter->setFont(m_font);
  181. painter->setPen(m_textColor);
  182. painter->drawText(m_boundingRect, Qt::AlignCenter, m_content);
  183. }
  184. // 文本消息实现
  185. TextMessage::TextMessage(const QString &text, MessageDirection direction, QGraphicsItem *parent)
  186. : BubbleMessage(text, MessageType::TextMessage, direction, parent)
  187. {
  188. m_textDocument.setDefaultFont(m_font);
  189. m_textDocument.setDocumentMargin(m_padding);
  190. m_textDocument.setHtml(text);
  191. // 确保接收鼠标事件
  192. setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
  193. setFlag(QGraphicsItem::ItemIsSelectable, true);
  194. updateSize();
  195. }
  196. void TextMessage::mousePressEvent(QGraphicsSceneMouseEvent *event)
  197. {
  198. if (event->button() == Qt::LeftButton) {
  199. QPointF pos = event->pos();
  200. if (m_boundingRect.contains(pos)) {
  201. m_isSelecting = true;
  202. m_selectionStart = positionAt(pos - QPointF(m_padding, m_padding));
  203. m_selectionEnd = m_selectionStart;
  204. m_hasSelection = false;
  205. update();
  206. }
  207. }
  208. BubbleMessage::mousePressEvent(event);
  209. }
  210. void TextMessage::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
  211. {
  212. if (m_isSelecting) {
  213. QPointF pos = event->pos();
  214. m_selectionEnd = positionAt(pos - QPointF(m_padding, m_padding));
  215. m_hasSelection = true;
  216. update();
  217. }
  218. BubbleMessage::mouseMoveEvent(event);
  219. }
  220. void TextMessage::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
  221. {
  222. if (m_isSelecting) {
  223. m_isSelecting = false;
  224. if (m_hasSelection && m_selectionStart != m_selectionEnd) {
  225. // 如果有选中文本,可以在这里显示复制菜单
  226. QMenu menu;
  227. QAction *copyAction = menu.addAction("复制选中文本");
  228. QAction *selectedAction = menu.exec(event->screenPos());
  229. if (selectedAction == copyAction) {
  230. QClipboard *clipboard = QApplication::clipboard();
  231. clipboard->setText(selectedText());
  232. }
  233. }
  234. }
  235. BubbleMessage::mouseReleaseEvent(event);
  236. }
  237. // 获取指定位置的文本位置
  238. int TextMessage::positionAt(const QPointF &pos) const
  239. {
  240. return m_textDocument.documentLayout()->hitTest(pos, Qt::FuzzyHit);
  241. }
  242. // 获取选中的文本
  243. QString TextMessage::selectedText()
  244. {
  245. if (!m_hasSelection || m_selectionStart == m_selectionEnd)
  246. return QString();
  247. QTextCursor cursor(&m_textDocument);
  248. int start = qMin(m_selectionStart, m_selectionEnd);
  249. int end = qMax(m_selectionStart, m_selectionEnd);
  250. cursor.setPosition(start);
  251. cursor.setPosition(end, QTextCursor::KeepAnchor);
  252. return cursor.selectedText();
  253. }
  254. void TextMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  255. {
  256. BubbleMessage::paint(painter, option, widget);
  257. painter->setFont(m_font);
  258. painter->setPen(m_textColor);
  259. // 保存画家状态
  260. painter->save();
  261. painter->translate(m_padding, m_padding);
  262. // 设置文本颜色
  263. QAbstractTextDocumentLayout::PaintContext context;
  264. context.palette.setColor(QPalette::Text, m_textColor);
  265. // 如果有选中区域,设置选中区域的格式
  266. if (m_hasSelection && m_selectionStart != m_selectionEnd) {
  267. QTextCursor cursor(&m_textDocument);
  268. int start = qMin(m_selectionStart, m_selectionEnd);
  269. int end = qMax(m_selectionStart, m_selectionEnd);
  270. cursor.setPosition(start);
  271. cursor.setPosition(end, QTextCursor::KeepAnchor);
  272. QAbstractTextDocumentLayout::Selection selection;
  273. selection.cursor = cursor;
  274. selection.format.setBackground(Qt::lightGray);
  275. selection.format.setForeground(Qt::black);
  276. context.selections.append(selection);
  277. }
  278. // 绘制文本
  279. m_textDocument.documentLayout()->draw(painter, context);
  280. // 恢复画家状态
  281. painter->restore();
  282. }
  283. void TextMessage::updateSize()
  284. {
  285. // 使用视图宽度而不是屏幕宽度
  286. int viewWidth = 500; // 默认值
  287. // 尝试获取实际视图宽度
  288. if (scene() && !scene()->views().isEmpty()) {
  289. viewWidth = scene()->views().first()->width();
  290. }
  291. // 使用视图宽度的60%,并减去边距和头像空间
  292. int maxWidth = viewWidth * 0.6 - m_avatarSize - 30; // 30为额外边距
  293. m_textDocument.setTextWidth(maxWidth - 2 * m_padding);
  294. QSizeF docSize = m_textDocument.size();
  295. m_boundingRect = QRectF(0, 0, docSize.width() + 2 * m_padding, docSize.height() + 2 * m_padding);
  296. }
  297. // 成功消息实现
  298. SuccessMessage::SuccessMessage(const QString &text,
  299. MessageDirection direction,
  300. QGraphicsItem *parent)
  301. : TextMessage(text, direction, parent)
  302. {
  303. m_type = MessageType::SuccessMessage;
  304. m_bubbleColor = QColor(76, 175, 80);
  305. m_textColor = Qt::white;
  306. }
  307. void SuccessMessage::paint(QPainter *painter,
  308. const QStyleOptionGraphicsItem *option,
  309. QWidget *widget)
  310. {
  311. TextMessage::paint(painter, option, widget);
  312. // 绘制成功图标
  313. painter->setPen(Qt::white);
  314. painter->setBrush(Qt::white);
  315. QRectF iconRect(m_boundingRect.right() - 25, m_boundingRect.top() + 5, 20, 20);
  316. painter->drawEllipse(iconRect.center(), 8, 8);
  317. QPainterPath checkPath;
  318. checkPath.moveTo(iconRect.center().x() - 4, iconRect.center().y());
  319. checkPath.lineTo(iconRect.center().x(), iconRect.center().y() + 4);
  320. checkPath.lineTo(iconRect.center().x() + 4, iconRect.center().y() - 4);
  321. QPen checkPen(Qt::green, 2);
  322. painter->setPen(checkPen);
  323. painter->drawPath(checkPath);
  324. }
  325. // 失败消息实现
  326. FailureMessage::FailureMessage(const QString &text,
  327. MessageDirection direction,
  328. QGraphicsItem *parent)
  329. : TextMessage(text, direction, parent)
  330. {
  331. m_type = MessageType::FailureMessage;
  332. m_bubbleColor = QColor(244, 67, 54);
  333. m_textColor = Qt::white;
  334. }
  335. void FailureMessage::paint(QPainter *painter,
  336. const QStyleOptionGraphicsItem *option,
  337. QWidget *widget)
  338. {
  339. TextMessage::paint(painter, option, widget);
  340. // 绘制失败图标
  341. painter->setPen(Qt::white);
  342. painter->setBrush(Qt::white);
  343. QRectF iconRect(m_boundingRect.right() - 25, m_boundingRect.top() + 5, 20, 20);
  344. painter->drawEllipse(iconRect.center(), 8, 8);
  345. QPen crossPen(Qt::red, 2);
  346. painter->setPen(crossPen);
  347. painter->drawLine(iconRect.center().x() - 4,
  348. iconRect.center().y() - 4,
  349. iconRect.center().x() + 4,
  350. iconRect.center().y() + 4);
  351. painter->drawLine(iconRect.center().x() - 4,
  352. iconRect.center().y() + 4,
  353. iconRect.center().x() + 4,
  354. iconRect.center().y() - 4);
  355. }
  356. // 文件消息实现
  357. FileMessage::FileMessage(const QString &fileName,
  358. const QString &filePath,
  359. qint64 fileSize,
  360. MessageDirection direction,
  361. QGraphicsItem *parent)
  362. : BubbleMessage(fileName, MessageType::FileMessage, direction, parent)
  363. , m_fileName(fileName)
  364. , m_filePath(filePath)
  365. , m_fileSize(fileSize)
  366. {
  367. // 根据文件类型获取图标
  368. QFileInfo fileInfo(filePath);
  369. QString suffix = fileInfo.suffix().toLower();
  370. // 这里可以根据不同的文件类型设置不同的图标
  371. if (suffix == "pdf") {
  372. m_fileIcon = QPixmap(":/icons/pdf.png");
  373. } else if (suffix == "doc" || suffix == "docx") {
  374. m_fileIcon = QPixmap(":/icons/word.png");
  375. } else if (suffix == "xls" || suffix == "xlsx") {
  376. m_fileIcon = QPixmap(":/icons/excel.png");
  377. } else if (suffix == "ppt" || suffix == "pptx") {
  378. m_fileIcon = QPixmap(":/icons/powerpoint.png");
  379. } else if (suffix == "zip" || suffix == "rar" || suffix == "7z") {
  380. m_fileIcon = QPixmap(":/icons/archive.png");
  381. } else {
  382. m_fileIcon = QPixmap(":/icons/file.png");
  383. }
  384. // 如果没有找到图标资源,使用默认图标
  385. if (m_fileIcon.isNull()) {
  386. m_fileIcon = QPixmap(32, 32);
  387. m_fileIcon.fill(Qt::transparent);
  388. QPainter p(&m_fileIcon);
  389. p.setPen(QPen(Qt::gray, 1));
  390. p.setBrush(Qt::white);
  391. p.drawRect(0, 0, 31, 31);
  392. p.drawText(QRect(0, 0, 32, 32), Qt::AlignCenter, "FILE");
  393. }
  394. updateSize();
  395. }
  396. void FileMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  397. {
  398. BubbleMessage::paint(painter, option, widget);
  399. // 绘制文件图标
  400. QRectF iconRect(m_padding, m_padding, 32, 32);
  401. painter->drawPixmap(iconRect, m_fileIcon, m_fileIcon.rect());
  402. // 绘制文件名
  403. painter->setPen(m_textColor);
  404. painter->setFont(QFont(m_font.family(), m_font.pointSize(), QFont::Bold));
  405. QRectF nameRect(iconRect.right() + 5,
  406. m_padding,
  407. m_boundingRect.width() - iconRect.right() - 10,
  408. 20);
  409. painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, m_fileName);
  410. // 绘制文件大小
  411. painter->setFont(QFont(m_font.family(), m_font.pointSize() - 1));
  412. QRectF sizeRect(iconRect.right() + 5, nameRect.bottom(), nameRect.width(), 20);
  413. // 格式化文件大小
  414. QString sizeStr;
  415. if (m_fileSize < 1024) {
  416. sizeStr = QString("%1 B").arg(m_fileSize);
  417. } else if (m_fileSize < 1024 * 1024) {
  418. sizeStr = QString("%1 KB").arg(m_fileSize / 1024.0, 0, 'f', 2);
  419. } else if (m_fileSize < 1024 * 1024 * 1024) {
  420. sizeStr = QString("%1 MB").arg(m_fileSize / (1024.0 * 1024.0), 0, 'f', 2);
  421. } else {
  422. sizeStr = QString("%1 GB").arg(m_fileSize / (1024.0 * 1024.0 * 1024.0), 0, 'f', 2);
  423. }
  424. painter->drawText(sizeRect, Qt::AlignLeft | Qt::AlignVCenter, sizeStr);
  425. }
  426. void FileMessage::updateSize()
  427. {
  428. // 文件消息的固定大小
  429. m_boundingRect = QRectF(0, 0, 250, 60);
  430. }
  431. // 撤回消息实现
  432. RecallMessage::RecallMessage(const QString &recalledBy,
  433. MessageDirection direction,
  434. QGraphicsItem *parent)
  435. : BubbleMessage(QString("%1 撤回了一条消息").arg(recalledBy),
  436. MessageType::RecallMessage,
  437. direction,
  438. parent)
  439. {
  440. m_bubbleColor = QColor(200, 200, 200, 150);
  441. m_textColor = QColor(100, 100, 100);
  442. updateSize();
  443. }
  444. void RecallMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  445. {
  446. BubbleMessage::paint(painter, option, widget);
  447. painter->setFont(m_font);
  448. painter->setPen(m_textColor);
  449. painter->drawText(m_boundingRect.adjusted(m_padding, m_padding, -m_padding, -m_padding),
  450. Qt::AlignCenter,
  451. m_content);
  452. }
  453. // 图片消息实现
  454. ImageMessage::ImageMessage(const QPixmap &image, MessageDirection direction, QGraphicsItem *parent)
  455. : BubbleMessage("", MessageType::ImageMessage, direction, parent)
  456. , m_image(image)
  457. {
  458. updateSize();
  459. }
  460. void ImageMessage::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
  461. {
  462. BubbleMessage::paint(painter, option, widget);
  463. // 绘制图片
  464. QRectF imageRect = m_boundingRect.adjusted(m_padding, m_padding, -m_padding, -m_padding);
  465. painter->drawPixmap(imageRect, m_scaledImage, m_scaledImage.rect());
  466. }
  467. void ImageMessage::updateSize()
  468. {
  469. // 限制最大尺寸
  470. int maxWidth = QGuiApplication::primaryScreen()->size().width() * 0.4;
  471. int maxHeight = 300;
  472. if (m_image.width() > maxWidth || m_image.height() > maxHeight) {
  473. m_scaledImage = m_image.scaled(maxWidth,
  474. maxHeight,
  475. Qt::KeepAspectRatio,
  476. Qt::SmoothTransformation);
  477. } else {
  478. m_scaledImage = m_image;
  479. }
  480. m_boundingRect = QRectF(0,
  481. 0,
  482. m_scaledImage.width() + 2 * m_padding,
  483. m_scaledImage.height() + 2 * m_padding);
  484. }
  485. // 文本图片混合消息实现
  486. TextImageMessage::TextImageMessage(const QString &text,
  487. const QPixmap &image,
  488. MessageDirection direction,
  489. QGraphicsItem *parent)
  490. : BubbleMessage(text, MessageType::TextImageMessage, direction, parent)
  491. , m_image(image)
  492. {
  493. m_textDocument.setDefaultFont(m_font);
  494. m_textDocument.setDocumentMargin(m_padding);
  495. m_textDocument.setHtml(text);
  496. updateSize();
  497. }
  498. void TextImageMessage::paint(QPainter *painter,
  499. const QStyleOptionGraphicsItem *option,
  500. QWidget *widget)
  501. {
  502. BubbleMessage::paint(painter, option, widget);
  503. // 保存画家状态
  504. painter->save();
  505. // 绘制文本
  506. painter->setPen(m_textColor);
  507. painter->translate(m_padding, m_padding);
  508. QAbstractTextDocumentLayout::PaintContext context;
  509. context.palette.setColor(QPalette::Text, m_textColor);
  510. m_textDocument.documentLayout()->draw(painter, context);
  511. // 恢复画家状态
  512. painter->restore();
  513. // 绘制图片
  514. QSizeF docSize = m_textDocument.size();
  515. QRectF imageRect(m_padding,
  516. m_padding + docSize.height() + 5,
  517. m_scaledImage.width(),
  518. m_scaledImage.height());
  519. painter->drawPixmap(imageRect, m_scaledImage, m_scaledImage.rect());
  520. }
  521. void TextImageMessage::updateSize()
  522. {
  523. // 限制最大宽度
  524. int maxWidth = QGuiApplication::primaryScreen()->size().width() * 0.4;
  525. m_textDocument.setTextWidth(maxWidth - 2 * m_padding);
  526. // 限制图片最大尺寸
  527. int maxImageWidth = maxWidth - 2 * m_padding;
  528. int maxImageHeight = 200;
  529. if (m_image.width() > maxImageWidth || m_image.height() > maxImageHeight) {
  530. m_scaledImage = m_image.scaled(maxImageWidth,
  531. maxImageHeight,
  532. Qt::KeepAspectRatio,
  533. Qt::SmoothTransformation);
  534. } else {
  535. m_scaledImage = m_image;
  536. }
  537. QSizeF docSize = m_textDocument.size();
  538. m_boundingRect = QRectF(0,
  539. 0,
  540. std::max((int) docSize.width() + 2 * m_padding,
  541. m_scaledImage.width() + 2 * m_padding),
  542. docSize.height() + m_scaledImage.height() + 2 * m_padding + 5);
  543. }
  544. // 消息场景实现
  545. MessageScene::MessageScene(QObject *parent)
  546. : QGraphicsScene(parent)
  547. , m_verticalSpacing(10)
  548. , m_horizontalMargin(20)
  549. {
  550. setBackgroundBrush(QColor(245, 245, 245));
  551. // 创建默认头像
  552. m_defaultSentAvatar = QPixmap(40, 40);
  553. m_defaultSentAvatar.fill(Qt::blue);
  554. m_defaultReceivedAvatar = QPixmap(40, 40);
  555. m_defaultReceivedAvatar.fill(Qt::green);
  556. }
  557. void MessageScene::addMessage(BubbleMessage *message)
  558. {
  559. if (message->messageType() != BubbleMessage::TimeMessage
  560. && message->messageType() != BubbleMessage::RecallMessage) {
  561. if (message->messageDirection() == BubbleMessage::Sent) {
  562. message->setAvatar(m_defaultSentAvatar);
  563. } else {
  564. message->setAvatar(m_defaultReceivedAvatar);
  565. }
  566. }
  567. // 存储消息时间戳用于排序
  568. message->setData(0, message->timestamp());
  569. addItem(message);
  570. // 直接放置新消息,而不是重新排列所有消息
  571. placeNewMessage(message);
  572. // 确保视图滚动到最新消息
  573. if (!views().isEmpty() && views().first()->verticalScrollBar()) {
  574. views().first()->verticalScrollBar()->setValue(
  575. views().first()->verticalScrollBar()->maximum());
  576. }
  577. }
  578. void MessageScene::placeNewMessage(BubbleMessage *message)
  579. {
  580. qreal sceneWidth = views().isEmpty() ? 500 : views().first()->width();
  581. // 获取当前场景中最后一条消息的位置
  582. qreal currentY = 10; // 默认起始位置
  583. if (!items().isEmpty()) {
  584. // 找到当前最底部的消息
  585. qreal maxY = 0;
  586. for (QGraphicsItem *item : items()) {
  587. BubbleMessage *msg = dynamic_cast<BubbleMessage *>(item);
  588. if (msg) {
  589. qreal itemBottom = msg->y() + msg->boundingRect().height();
  590. if (itemBottom > maxY) {
  591. maxY = itemBottom;
  592. }
  593. }
  594. }
  595. currentY = maxY + m_verticalSpacing;
  596. }
  597. // 计算消息的X坐标
  598. qreal x;
  599. if (message->messageType() == BubbleMessage::TimeMessage
  600. || message->messageType() == BubbleMessage::RecallMessage) {
  601. // 时间消息和撤回消息居中显示
  602. x = (sceneWidth - message->boundingRect().width()) / 2;
  603. } else if (message->messageDirection() == BubbleMessage::Sent) {
  604. // 发送的消息靠右显示,但要留出头像的空间
  605. x = sceneWidth - message->boundingRect().width() - m_horizontalMargin;
  606. } else {
  607. // 接收的消息靠左显示,考虑头像空间
  608. x = m_horizontalMargin + message->avatar().width() + 10;
  609. }
  610. // 设置消息位置
  611. message->setPos(x, currentY);
  612. // 更新场景高度,确保能看到所有消息
  613. QRectF currentRect = sceneRect();
  614. qreal newHeight = currentY + message->boundingRect().height() + m_verticalSpacing;
  615. if (newHeight > currentRect.height()) {
  616. setSceneRect(0, 0, sceneWidth, newHeight);
  617. }
  618. }
  619. void MessageScene::clear()
  620. {
  621. QGraphicsScene::clear();
  622. }
  623. void MessageScene::setDefaultAvatars(const QPixmap &sentAvatar, const QPixmap &receivedAvatar)
  624. {
  625. m_defaultSentAvatar = sentAvatar;
  626. m_defaultReceivedAvatar = receivedAvatar;
  627. }
  628. void MessageScene::arrangeMessages()
  629. {
  630. // 获取所有消息项
  631. QList<QGraphicsItem *> items = this->items();
  632. // 按照时间戳排序(从早到晚)
  633. std::sort(items.begin(), items.end(), [](QGraphicsItem *a, QGraphicsItem *b) {
  634. return a->data(0).toDateTime() < b->data(0).toDateTime();
  635. });
  636. qreal currentY = 10;
  637. qreal sceneWidth = views().isEmpty() ? 500 : views().first()->width();
  638. for (QGraphicsItem *item : items) {
  639. BubbleMessage *message = dynamic_cast<BubbleMessage *>(item);
  640. if (!message)
  641. continue;
  642. qreal x;
  643. if (message->messageType() == BubbleMessage::TimeMessage
  644. || message->messageType() == BubbleMessage::RecallMessage) {
  645. // 时间消息和撤回消息居中显示
  646. x = (sceneWidth - message->boundingRect().width()) / 2;
  647. } else if (message->messageDirection() == BubbleMessage::Sent) {
  648. // 发送的消息靠右显示,但要留出头像的空间
  649. x = sceneWidth - message->boundingRect().width() - m_horizontalMargin;
  650. } else {
  651. // 接收的消息靠左显示,考虑头像空间
  652. x = m_horizontalMargin + message->avatar().width() + 10;
  653. }
  654. message->setPos(x, currentY);
  655. currentY += message->boundingRect().height() + m_verticalSpacing;
  656. }
  657. // 更新场景矩形
  658. setSceneRect(0, 0, sceneWidth, currentY);
  659. }
  660. // 消息视图实现
  661. BubbleMessageView::BubbleMessageView(QWidget *parent)
  662. : QGraphicsView(parent)
  663. , m_autoScroll(true)
  664. {
  665. m_scene = new MessageScene(this);
  666. setScene(m_scene);
  667. // 设置视图属性
  668. setRenderHint(QPainter::Antialiasing);
  669. setRenderHint(QPainter::SmoothPixmapTransform);
  670. setRenderHint(QPainter::TextAntialiasing);
  671. setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  672. setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
  673. // 设置视图背景
  674. setBackgroundBrush(QColor(245, 245, 245));
  675. // 设置拖拽模式
  676. setDragMode(QGraphicsView::NoDrag);
  677. // 优化性能
  678. setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
  679. setCacheMode(QGraphicsView::CacheBackground);
  680. setOptimizationFlags(QGraphicsView::DontAdjustForAntialiasing
  681. | QGraphicsView::DontSavePainterState);
  682. }
  683. void BubbleMessageView::addTimeMessage(const QDateTime &time)
  684. {
  685. class TimeMessage *message = new class TimeMessage(time);
  686. m_scene->addMessage(message);
  687. if (m_autoScroll) {
  688. verticalScrollBar()->setValue(verticalScrollBar()->maximum());
  689. }
  690. }
  691. void BubbleMessageView::addTextMessage(const QString &text,
  692. BubbleMessage::MessageDirection direction)
  693. {
  694. class TextMessage *message = new class TextMessage(text, direction);
  695. m_scene->addMessage(message);
  696. // if (m_autoScroll) {
  697. // verticalScrollBar()->setValue(verticalScrollBar()->maximum());
  698. // }
  699. }
  700. void BubbleMessageView::addSuccessMessage(const QString &text,
  701. BubbleMessage::MessageDirection direction)
  702. {
  703. class SuccessMessage *message = new class SuccessMessage(text, direction);
  704. m_scene->addMessage(message);
  705. if (m_autoScroll) {
  706. verticalScrollBar()->setValue(verticalScrollBar()->maximum());
  707. }
  708. }
  709. void BubbleMessageView::addFailureMessage(const QString &text,
  710. BubbleMessage::MessageDirection direction)
  711. {
  712. class FailureMessage *message = new class FailureMessage(text, direction);
  713. m_scene->addMessage(message);
  714. if (m_autoScroll) {
  715. verticalScrollBar()->setValue(verticalScrollBar()->maximum());
  716. }
  717. }
  718. void BubbleMessageView::addFileMessage(const QString &fileName,
  719. const QString &filePath,
  720. qint64 fileSize,
  721. BubbleMessage::MessageDirection direction)
  722. {
  723. class FileMessage *message = new class FileMessage(fileName, filePath, fileSize, direction);
  724. m_scene->addMessage(message);
  725. if (m_autoScroll) {
  726. verticalScrollBar()->setValue(verticalScrollBar()->maximum());
  727. }
  728. }
  729. void BubbleMessageView::addRecallMessage(const QString &recalledBy,
  730. BubbleMessage::MessageDirection direction)
  731. {
  732. class RecallMessage *message = new class RecallMessage(recalledBy, direction);
  733. m_scene->addMessage(message);
  734. if (m_autoScroll) {
  735. verticalScrollBar()->setValue(verticalScrollBar()->maximum());
  736. }
  737. }
  738. void BubbleMessageView::addImageMessage(const QPixmap &image,
  739. BubbleMessage::MessageDirection direction)
  740. {
  741. class ImageMessage *message = new class ImageMessage(image, direction);
  742. m_scene->addMessage(message);
  743. if (m_autoScroll) {
  744. verticalScrollBar()->setValue(verticalScrollBar()->maximum());
  745. }
  746. }
  747. void BubbleMessageView::addTextImageMessage(const QString &text,
  748. const QPixmap &image,
  749. BubbleMessage::MessageDirection direction)
  750. {
  751. class TextImageMessage *message = new class TextImageMessage(text, image, direction);
  752. m_scene->addMessage(message);
  753. if (m_autoScroll) {
  754. verticalScrollBar()->setValue(verticalScrollBar()->maximum());
  755. }
  756. }
  757. void BubbleMessageView::clearMessages()
  758. {
  759. m_scene->clear();
  760. }
  761. void BubbleMessageView::resizeEvent(QResizeEvent *event)
  762. {
  763. QGraphicsView::resizeEvent(event);
  764. // 调整场景大小以适应视图
  765. QRectF sceneRect = m_scene->sceneRect();
  766. sceneRect.setWidth(viewport()->width());
  767. m_scene->setSceneRect(sceneRect);
  768. // 重新排列消息
  769. m_scene->arrangeMessages();
  770. }
  771. void BubbleMessageView::wheelEvent(QWheelEvent *event)
  772. {
  773. // 检测用户是否手动滚动
  774. int maxValue = verticalScrollBar()->maximum();
  775. int currentValue = verticalScrollBar()->value();
  776. QGraphicsView::wheelEvent(event);
  777. // 如果用户向上滚动,禁用自动滚动
  778. if (event->angleDelta().y() > 0 && currentValue < maxValue) {
  779. m_autoScroll = false;
  780. }
  781. // 如果用户滚动到底部,启用自动滚动
  782. if (verticalScrollBar()->value() >= verticalScrollBar()->maximum()) {
  783. m_autoScroll = true;
  784. }
  785. }