chatview.cpp 9.9 KB

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