#include "chatview.h" #include "qdebug.h" #include "qmenu.h" #include "widgets/chatView/chat1/chatmessage.h" #include #include #include #include #include #include // 在构造函数中添加 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(); // 系统消息不支持选择 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(); // 系统消息不支持选择 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(); // 系统消息不支持选择 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(); 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::addImageMessage(const QString &imagePath, const QString &avatar, const QString &senderName, bool isLeft, const QString &text) { // 获取图片尺寸 QPixmap image(imagePath); QSize imageSize = image.isNull() ? QSize(100, 100) : image.size(); ChatMessage message(imagePath, imageSize, isLeft ? MessageType::Left : MessageType::Right, text); message.avatar = avatar; message.senderName = senderName; m_model->addMessage(message); scrollToBottom(); } void ChatView::clear() { m_model->clear(); }