| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- #include "chatview.h"
- #include "qdebug.h"
- #include "qmenu.h"
- #include "widgets/chatView/chat1/chatmessage.h"
- #include <QApplication>
- #include <QClipboard>
- #include <QMouseEvent>
- #include <QTimer>
- #include <utility>
- // 在构造函数中添加
- ChatView::ChatView(QWidget *parent)
- : QListView(parent)
- , m_selecting(false)
- {
- m_model = new ChatMessageModel(this);
- m_delegate = new ChatMessageDelegate(this);
- setModel(m_model);
- setItemDelegate(m_delegate);
- setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
- setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
- setSpacing(0);
- // 启用文本选择
- setSelectionMode(QAbstractItemView::ExtendedSelection);
- setTextElideMode(Qt::ElideNone);
- // 优化性能
- setUniformItemSizes(false);
- // 设置鼠标追踪,以支持文本选择
- setMouseTracking(true);
-
- // 创建右键菜单
- m_contextMenu = new QMenu(this);
- QAction *copyAction = m_contextMenu->addAction("复制");
- connect(copyAction, &QAction::triggered, this, &ChatView::copySelectedText);
- connect(this,
- &ChatView::viewportWidthChanged,
- m_delegate,
- &ChatMessageDelegate::setViewportWidth);
- setMinimumWidth(ChatConstants::BUBBLE_PADDING + ChatConstants::AVATAR_SIZE + 150
- + ChatConstants::AVATAR_SIZE + ChatConstants::BUBBLE_PADDING);
- }
- void ChatView::mousePressEvent(QMouseEvent *event)
- {
- if (event->button() == Qt::LeftButton) {
- m_selecting = true;
- QModelIndex index = indexAt(event->pos());
- if (index.isValid()) {
- const ChatMessage &message = index.data().value<ChatMessage>();
- // 系统消息不支持选择
- if (message.type == MessageType::System) {
- m_selecting = false;
- QListView::mousePressEvent(event);
- return;
- }
- m_currentMessageIndex = index;
- QRect itemRect = visualRect(index);
- QPoint relativePos = event->pos() - itemRect.topLeft();
- // 计算文本区域的偏移
- if (message.type == MessageType::Left) {
- relativePos.rx() -= (ChatConstants::AVATAR_SIZE + 2 * ChatConstants::BUBBLE_SPACING);
- } else {
- // 右侧消息需要特殊处理
- QSize textSize = m_delegate->calculateTextSize(fontMetrics(), message.text);
- int bubbleWidth = textSize.width() + 2 * ChatConstants::BUBBLE_PADDING;
- relativePos.rx() -= (itemRect.width() - bubbleWidth - ChatConstants::AVATAR_SIZE
- - 2 * ChatConstants::BUBBLE_SPACING);
- }
- // 调整相对于文本的位置
- relativePos.rx() -= ChatConstants::BUBBLE_PADDING;
- relativePos.ry() -= ChatConstants::BUBBLE_PADDING;
- // 通过委托计算文本位置
- int pos = m_delegate->getPositionFromPoint(relativePos, message.text, fontMetrics());
- // 优化:如果点击位置在气泡内但不在文本上,或者位置小于等于0,则选择从第一个字符开始
- if (pos <= 0 //
- && relativePos.x() >= -ChatConstants::BUBBLE_PADDING //
- && relativePos.y() >= -ChatConstants::BUBBLE_PADDING //
- && relativePos.x() <= message.text.length() * 8 && // 粗略估计文本宽度
- relativePos.y() <= fontMetrics().height() * (message.text.count('\n') + 1)) {
- pos = 0; // 从第一个字符开始选择
- }
- // 确保点击位置有效,才设置选择起点
- if (pos >= 0 && pos <= message.text.length()) {
- m_selectionStartPos = pos;
- m_selectionEndPos = pos;
- // 更新委托的选择信息
- m_delegate->setSelectionRange(m_selectionStartPos, m_selectionEndPos);
- m_delegate->setCurrentMessageIndex(m_currentMessageIndex);
- // 设置当前选中项
- setCurrentIndex(index);
- update(index); // 重绘当前项
- } else {
- // 点击在文本区域外,不进行选择
- m_selecting = false;
- }
- } else {
- // 点击空白区域,清除选择
- m_selectionStartPos = -1;
- m_selectionEndPos = -1;
- m_currentMessageIndex = QModelIndex();
- m_delegate->setSelectionRange(-1, -1);
- m_delegate->setCurrentMessageIndex(QModelIndex());
- }
- }
- QListView::mousePressEvent(event);
- }
- void ChatView::mouseMoveEvent(QMouseEvent *event)
- {
- if (m_selecting && m_currentMessageIndex.isValid()) {
- QRect itemRect = visualRect(m_currentMessageIndex);
- QPoint relativePos = event->pos() - itemRect.topLeft();
- // 计算文本区域的偏移
- const ChatMessage &message = m_currentMessageIndex.data().value<ChatMessage>();
- // 系统消息不支持选择
- if (message.type == MessageType::System) {
- return;
- }
- if (message.type == MessageType::Left) {
- relativePos.rx() -= (ChatConstants::AVATAR_SIZE + 2 * ChatConstants::BUBBLE_SPACING);
- } else {
- // 右侧消息需要特殊处理
- QSize textSize = m_delegate->calculateTextSize(fontMetrics(), message.text);
- int bubbleWidth = textSize.width() + 2 * ChatConstants::BUBBLE_PADDING;
- relativePos.rx() -= (itemRect.width() - bubbleWidth - ChatConstants::AVATAR_SIZE
- - 2 * ChatConstants::BUBBLE_SPACING);
- }
- // 调整相对于文本的位置
- relativePos.rx() -= ChatConstants::BUBBLE_PADDING;
- relativePos.ry() -= ChatConstants::BUBBLE_PADDING;
- int newPos = m_delegate->getPositionFromPoint(relativePos, message.text, fontMetrics());
- // 确保新位置有效
- if (newPos >= 0 && newPos <= message.text.length()) {
- m_selectionEndPos = newPos;
- // 更新委托的选择信息
- m_delegate->setSelectionRange(m_selectionStartPos, m_selectionEndPos);
- update(m_currentMessageIndex); // 重绘当前项
- }
- }
- QListView::mouseMoveEvent(event);
- }
- // 新增上下文菜单事件
- void ChatView::contextMenuEvent(QContextMenuEvent *event)
- {
- if (m_currentMessageIndex.isValid() && m_selectionStartPos >= 0 && m_selectionEndPos >= 0
- && m_selectionStartPos != m_selectionEndPos) {
- m_contextMenu->exec(event->globalPos());
- } else {
- QListView::contextMenuEvent(event);
- }
- }
- void ChatView::resizeEvent(QResizeEvent *event)
- {
- QListView::resizeEvent(event);
- // 当视图大小改变时,通知委托视图宽度已更改
- emit viewportWidthChanged(viewport()->width());
- // 强制更新所有项目的布局,解决重叠问题
- for (int i = 0; i < m_model->rowCount(); ++i) {
- QModelIndex index = m_model->index(i);
- update(index);
- }
- // 强制模型发出数据变化信号,触发重新布局
- if (m_model->rowCount() > 0) {
- m_model->dataChanged(m_model->index(0), m_model->index(m_model->rowCount() - 1));
- }
- // 当视图大小改变时,保持滚动到底部
- if (m_model->rowCount() > 0) {
- QModelIndex lastIndex = m_model->index(m_model->rowCount() - 1);
- scrollTo(lastIndex);
- }
- }
- // 新增文本获取方法
- QString ChatView::getSelectedText() const
- {
- if (m_currentMessageIndex.isValid() && m_selectionStartPos >= 0 && m_selectionEndPos >= 0) {
- const ChatMessage message = m_currentMessageIndex.data().value<ChatMessage>();
- // 系统消息不支持选择
- if (message.type == MessageType::System) {
- return QString();
- }
- const QString text = message.text;
- int start = qMin(m_selectionStartPos, m_selectionEndPos);
- int end = qMax(m_selectionStartPos, m_selectionEndPos);
- if (start != end && start >= 0 && end <= text.length()) {
- // 直接提取选中的文本,保留原始格式包括换行符
- return text.mid(start, end - start);
- }
- }
- return QString();
- }
- // 新增复制方法
- void ChatView::copySelectedText()
- {
- QString text = getSelectedText();
- if (!text.isEmpty()) {
- QClipboard *clipboard = QApplication::clipboard();
- clipboard->setText(text);
- }
- }
- void ChatView::scrollToBottom()
- {
- QTimer::singleShot(0, this, [this]() {
- if (m_model->rowCount() > 0) {
- scrollTo(m_model->index(m_model->rowCount() - 1), QAbstractItemView::PositionAtBottom);
- }
- });
- }
- void ChatView::mouseReleaseEvent(QMouseEvent *event)
- {
- if (event->button() == Qt::LeftButton) {
- m_selecting = false;
- }
- QListView::mouseReleaseEvent(event);
- }
- void ChatView::keyPressEvent(QKeyEvent *event)
- {
- // 处理复制操作
- if (event->matches(QKeySequence::Copy)) {
- QStringList selectedTexts;
- QModelIndexList selection = selectionModel()->selectedIndexes();
- for (const QModelIndex &index : std::as_const(selection)) {
- ChatMessage message = index.data().value<ChatMessage>();
- selectedTexts << message.text;
- }
- if (!selectedTexts.isEmpty()) {
- QClipboard *clipboard = QApplication::clipboard();
- clipboard->setText(selectedTexts.join("\n"));
- }
- event->accept();
- return;
- }
- QListView::keyPressEvent(event);
- }
- void ChatView::addMessage(const QString &text, const QString &avatar, const QString &senderName, bool isLeft)
- {
- ChatMessage message;
- message.text = text;
- message.avatar = avatar;
- message.senderName = senderName;
- message.type = isLeft ? MessageType::Left : MessageType::Right;
- m_model->addMessage(message);
- scrollToBottom();
- }
- void ChatView::addSystemMessage(const QString &text)
- {
- ChatMessage message(text, MessageType::System);
- m_model->addMessage(message);
- scrollToBottom();
- }
- void ChatView::clear()
- {
- m_model->clear();
- }
|