chatview.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. #include "chatview.h"
  2. #include "qdebug.h"
  3. #include "qmenu.h"
  4. #include "widgets/chatView/chat1/chatmessage.h"
  5. #include <QApplication>
  6. #include <QClipboard>
  7. #include <QMouseEvent>
  8. #include <QPixmap>
  9. #include <QTimer>
  10. #include <utility>
  11. // 在构造函数中添加
  12. ChatView::ChatView(QWidget *parent)
  13. : QListView(parent)
  14. , m_selecting(false)
  15. {
  16. m_model = new ChatMessageModel(this);
  17. m_delegate = new ChatMessageDelegate(this);
  18. setModel(m_model);
  19. setItemDelegate(m_delegate);
  20. setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
  21. setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  22. setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
  23. setSpacing(0);
  24. // 启用文本选择
  25. setSelectionMode(QAbstractItemView::ExtendedSelection);
  26. setTextElideMode(Qt::ElideNone);
  27. // 优化性能
  28. setUniformItemSizes(false);
  29. // 设置鼠标追踪,以支持文本选择
  30. setMouseTracking(true);
  31. // 创建右键菜单
  32. m_contextMenu = new QMenu(this);
  33. QAction *copyAction = m_contextMenu->addAction("复制");
  34. connect(copyAction, &QAction::triggered, this, &ChatView::copySelectedText);
  35. connect(this,
  36. &ChatView::viewportWidthChanged,
  37. m_delegate,
  38. &ChatMessageDelegate::setViewportWidth);
  39. setMinimumWidth(ChatConstants::BUBBLE_PADDING + ChatConstants::AVATAR_SIZE + 150
  40. + ChatConstants::AVATAR_SIZE + ChatConstants::BUBBLE_PADDING);
  41. }
  42. void ChatView::mousePressEvent(QMouseEvent *event)
  43. {
  44. if (event->button() == Qt::LeftButton) {
  45. m_selecting = true;
  46. QModelIndex index = indexAt(event->pos());
  47. if (index.isValid()) {
  48. const ChatMessage &message = index.data().value<ChatMessage>();
  49. // 系统消息不支持选择
  50. if (message.type == MessageType::System) {
  51. m_selecting = false;
  52. QListView::mousePressEvent(event);
  53. return;
  54. }
  55. m_currentMessageIndex = index;
  56. QRect itemRect = visualRect(index);
  57. QPoint relativePos = event->pos() - itemRect.topLeft();
  58. // 计算文本区域的偏移
  59. if (message.type == MessageType::Left) {
  60. relativePos.rx() -= (ChatConstants::AVATAR_SIZE + 2 * ChatConstants::BUBBLE_SPACING);
  61. } else {
  62. // 右侧消息需要特殊处理
  63. QSize textSize = m_delegate->calculateTextSize(fontMetrics(), message.text);
  64. int bubbleWidth = textSize.width() + 2 * ChatConstants::BUBBLE_PADDING;
  65. relativePos.rx() -= (itemRect.width() - bubbleWidth - ChatConstants::AVATAR_SIZE
  66. - 2 * ChatConstants::BUBBLE_SPACING);
  67. }
  68. // 调整相对于文本的位置
  69. relativePos.rx() -= ChatConstants::BUBBLE_PADDING;
  70. relativePos.ry() -= ChatConstants::BUBBLE_PADDING;
  71. // 通过委托计算文本位置
  72. int pos = m_delegate->getPositionFromPoint(relativePos, message.text, fontMetrics());
  73. // 优化:如果点击位置在气泡内但不在文本上,或者位置小于等于0,则选择从第一个字符开始
  74. if (pos <= 0 //
  75. && relativePos.x() >= -ChatConstants::BUBBLE_PADDING //
  76. && relativePos.y() >= -ChatConstants::BUBBLE_PADDING //
  77. && relativePos.x() <= message.text.length() * 8 && // 粗略估计文本宽度
  78. relativePos.y() <= fontMetrics().height() * (message.text.count('\n') + 1)) {
  79. pos = 0; // 从第一个字符开始选择
  80. }
  81. // 确保点击位置有效,才设置选择起点
  82. if (pos >= 0 && pos <= message.text.length()) {
  83. m_selectionStartPos = pos;
  84. m_selectionEndPos = pos;
  85. // 更新委托的选择信息
  86. m_delegate->setSelectionRange(m_selectionStartPos, m_selectionEndPos);
  87. m_delegate->setCurrentMessageIndex(m_currentMessageIndex);
  88. // 设置当前选中项
  89. setCurrentIndex(index);
  90. update(index); // 重绘当前项
  91. } else {
  92. // 点击在文本区域外,不进行选择
  93. m_selecting = false;
  94. }
  95. } else {
  96. // 点击空白区域,清除选择
  97. m_selectionStartPos = -1;
  98. m_selectionEndPos = -1;
  99. m_currentMessageIndex = QModelIndex();
  100. m_delegate->setSelectionRange(-1, -1);
  101. m_delegate->setCurrentMessageIndex(QModelIndex());
  102. }
  103. }
  104. QListView::mousePressEvent(event);
  105. }
  106. void ChatView::mouseMoveEvent(QMouseEvent *event)
  107. {
  108. if (m_selecting && m_currentMessageIndex.isValid()) {
  109. QRect itemRect = visualRect(m_currentMessageIndex);
  110. QPoint relativePos = event->pos() - itemRect.topLeft();
  111. // 计算文本区域的偏移
  112. const ChatMessage &message = m_currentMessageIndex.data().value<ChatMessage>();
  113. // 系统消息不支持选择
  114. if (message.type == MessageType::System) {
  115. return;
  116. }
  117. if (message.type == MessageType::Left) {
  118. relativePos.rx() -= (ChatConstants::AVATAR_SIZE + 2 * ChatConstants::BUBBLE_SPACING);
  119. } else {
  120. // 右侧消息需要特殊处理
  121. QSize textSize = m_delegate->calculateTextSize(fontMetrics(), message.text);
  122. int bubbleWidth = textSize.width() + 2 * ChatConstants::BUBBLE_PADDING;
  123. relativePos.rx() -= (itemRect.width() - bubbleWidth - ChatConstants::AVATAR_SIZE
  124. - 2 * ChatConstants::BUBBLE_SPACING);
  125. }
  126. // 调整相对于文本的位置
  127. relativePos.rx() -= ChatConstants::BUBBLE_PADDING;
  128. relativePos.ry() -= ChatConstants::BUBBLE_PADDING;
  129. int newPos = m_delegate->getPositionFromPoint(relativePos, message.text, fontMetrics());
  130. // 确保新位置有效
  131. if (newPos >= 0 && newPos <= message.text.length()) {
  132. m_selectionEndPos = newPos;
  133. // 更新委托的选择信息
  134. m_delegate->setSelectionRange(m_selectionStartPos, m_selectionEndPos);
  135. update(m_currentMessageIndex); // 重绘当前项
  136. }
  137. }
  138. QListView::mouseMoveEvent(event);
  139. }
  140. // 新增上下文菜单事件
  141. void ChatView::contextMenuEvent(QContextMenuEvent *event)
  142. {
  143. if (m_currentMessageIndex.isValid() && m_selectionStartPos >= 0 && m_selectionEndPos >= 0
  144. && m_selectionStartPos != m_selectionEndPos) {
  145. m_contextMenu->exec(event->globalPos());
  146. } else {
  147. QListView::contextMenuEvent(event);
  148. }
  149. }
  150. void ChatView::resizeEvent(QResizeEvent *event)
  151. {
  152. QListView::resizeEvent(event);
  153. // 当视图大小改变时,通知委托视图宽度已更改
  154. emit viewportWidthChanged(viewport()->width());
  155. // 强制更新所有项目的布局,解决重叠问题
  156. for (int i = 0; i < m_model->rowCount(); ++i) {
  157. QModelIndex index = m_model->index(i);
  158. update(index);
  159. }
  160. // 强制模型发出数据变化信号,触发重新布局
  161. if (m_model->rowCount() > 0) {
  162. m_model->dataChanged(m_model->index(0), m_model->index(m_model->rowCount() - 1));
  163. }
  164. // 当视图大小改变时,保持滚动到底部
  165. if (m_model->rowCount() > 0) {
  166. QModelIndex lastIndex = m_model->index(m_model->rowCount() - 1);
  167. scrollTo(lastIndex);
  168. }
  169. }
  170. // 新增文本获取方法
  171. QString ChatView::getSelectedText() const
  172. {
  173. if (m_currentMessageIndex.isValid() && m_selectionStartPos >= 0 && m_selectionEndPos >= 0) {
  174. const ChatMessage message = m_currentMessageIndex.data().value<ChatMessage>();
  175. // 系统消息不支持选择
  176. if (message.type == MessageType::System) {
  177. return QString();
  178. }
  179. const QString text = message.text;
  180. int start = qMin(m_selectionStartPos, m_selectionEndPos);
  181. int end = qMax(m_selectionStartPos, m_selectionEndPos);
  182. if (start != end && start >= 0 && end <= text.length()) {
  183. // 直接提取选中的文本,保留原始格式包括换行符
  184. return text.mid(start, end - start);
  185. }
  186. }
  187. return QString();
  188. }
  189. // 新增复制方法
  190. void ChatView::copySelectedText()
  191. {
  192. QString text = getSelectedText();
  193. if (!text.isEmpty()) {
  194. QClipboard *clipboard = QApplication::clipboard();
  195. clipboard->setText(text);
  196. }
  197. }
  198. void ChatView::scrollToBottom()
  199. {
  200. QTimer::singleShot(0, this, [this]() {
  201. if (m_model->rowCount() > 0) {
  202. scrollTo(m_model->index(m_model->rowCount() - 1), QAbstractItemView::PositionAtBottom);
  203. }
  204. });
  205. }
  206. void ChatView::mouseReleaseEvent(QMouseEvent *event)
  207. {
  208. if (event->button() == Qt::LeftButton) {
  209. m_selecting = false;
  210. }
  211. QListView::mouseReleaseEvent(event);
  212. }
  213. void ChatView::keyPressEvent(QKeyEvent *event)
  214. {
  215. // 处理复制操作
  216. if (event->matches(QKeySequence::Copy)) {
  217. QStringList selectedTexts;
  218. QModelIndexList selection = selectionModel()->selectedIndexes();
  219. for (const QModelIndex &index : std::as_const(selection)) {
  220. ChatMessage message = index.data().value<ChatMessage>();
  221. selectedTexts << message.text;
  222. }
  223. if (!selectedTexts.isEmpty()) {
  224. QClipboard *clipboard = QApplication::clipboard();
  225. clipboard->setText(selectedTexts.join("\n"));
  226. }
  227. event->accept();
  228. return;
  229. }
  230. QListView::keyPressEvent(event);
  231. }
  232. void ChatView::addMessage(const QString &text, const QString &avatar, const QString &senderName, bool isLeft)
  233. {
  234. ChatMessage message;
  235. message.text = text;
  236. message.avatar = avatar;
  237. message.senderName = senderName;
  238. message.type = isLeft ? MessageType::Left : MessageType::Right;
  239. m_model->addMessage(message);
  240. scrollToBottom();
  241. }
  242. void ChatView::addSystemMessage(const QString &text)
  243. {
  244. ChatMessage message(text, MessageType::System);
  245. m_model->addMessage(message);
  246. scrollToBottom();
  247. }
  248. void ChatView::addImageMessage(const QString &imagePath, const QString &avatar, const QString &senderName, bool isLeft, const QString &text)
  249. {
  250. // 获取图片尺寸
  251. QPixmap image(imagePath);
  252. QSize imageSize = image.isNull() ? QSize(100, 100) : image.size();
  253. ChatMessage message(imagePath, imageSize, isLeft ? MessageType::Left : MessageType::Right, text);
  254. message.avatar = avatar;
  255. message.senderName = senderName;
  256. m_model->addMessage(message);
  257. scrollToBottom();
  258. }
  259. void ChatView::clear()
  260. {
  261. m_model->clear();
  262. }