chatview.cpp 10 KB

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