You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
5.8 KiB
191 lines
5.8 KiB
#include "chatview.h" |
|
#include "messagedelegate.h" |
|
#include <QPainter> |
|
#include <QClipboard> |
|
#include <QApplication> |
|
#include <QDebug> |
|
#include <QMouseEvent> |
|
#include <QKeyEvent> |
|
#include <QScrollBar> |
|
|
|
ChatView::ChatView(QWidget *parent) |
|
: QListView(parent) |
|
{ |
|
setMouseTracking(true); |
|
|
|
m_scrollDownBtn = new QPushButton(QStringLiteral("\u25BC"), this); |
|
m_scrollDownBtn->setFixedSize(36, 36); |
|
m_scrollDownBtn->setCursor(Qt::PointingHandCursor); |
|
m_scrollDownBtn->setStyleSheet( |
|
"QPushButton {" |
|
" background: rgba(60,60,60,180);" |
|
" color: #ffffff;" |
|
" border: none;" |
|
" border-radius: 18px;" |
|
" font-size: 16px;" |
|
"}" |
|
"QPushButton:hover {" |
|
" background: rgba(90,90,90,220);" |
|
"}" |
|
); |
|
m_scrollDownBtn->setToolTip(tr("Scroll to bottom")); |
|
m_scrollDownBtn->setVisible(false); |
|
|
|
connect(m_scrollDownBtn, &QPushButton::clicked, this, [this]() { |
|
scrollToBottom(); |
|
}); |
|
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &ChatView::updateScrollDownBtn); |
|
connect(verticalScrollBar(), &QScrollBar::rangeChanged, this, &ChatView::updateScrollDownBtn); |
|
|
|
m_scrollDownBtn->raise(); |
|
} |
|
|
|
void ChatView::setBackgroundImage(const QString &path) { |
|
if (!m_background.load(path)) { |
|
qWarning() << "ChatView: failed to load background" << path; |
|
return; |
|
} |
|
viewport()->update(); |
|
} |
|
|
|
void ChatView::resizeEvent(QResizeEvent *event) { |
|
QListView::resizeEvent(event); |
|
scheduleDelayedItemsLayout(); |
|
updateScrollDownBtn(); |
|
} |
|
|
|
void ChatView::scrollContentsBy(int dx, int dy) { |
|
QListView::scrollContentsBy(dx, dy); |
|
viewport()->update(); |
|
} |
|
|
|
void ChatView::paintEvent(QPaintEvent *event) { |
|
if (!m_background.isNull()) { |
|
QPainter p(viewport()); |
|
p.drawTiledPixmap(viewport()->rect(), m_background); |
|
} |
|
QListView::paintEvent(event); |
|
} |
|
|
|
bool ChatView::isRowSelected(const QModelIndex &idx) const { |
|
if (!m_selStart.isValid() || !m_selEnd.isValid() || !idx.isValid()) |
|
return false; |
|
int r = idx.row(); |
|
int a = m_selStart.row(); |
|
int b = m_selEnd.row(); |
|
if (a > b) qSwap(a, b); |
|
return r >= a && r <= b; |
|
} |
|
|
|
void ChatView::mousePressEvent(QMouseEvent *event) { |
|
if (event->button() != Qt::LeftButton) { |
|
QListView::mousePressEvent(event); |
|
return; |
|
} |
|
if (hasSelection()) { |
|
m_selStart = QPersistentModelIndex(); |
|
m_selEnd = QPersistentModelIndex(); |
|
m_textSel = ChatTextSelection{}; |
|
m_selecting = false; |
|
viewport()->update(); |
|
} |
|
QModelIndex idx = indexAt(viewport()->mapFrom(this, event->pos())); |
|
m_pressIndex = QPersistentModelIndex(idx); |
|
m_pressPos = event->pos(); |
|
QListView::mousePressEvent(event); |
|
} |
|
|
|
void ChatView::mouseMoveEvent(QMouseEvent *event) { |
|
if (!m_selecting && m_pressIndex.isValid()) { |
|
int dx = event->pos().x() - m_pressPos.x(); |
|
int dy = event->pos().y() - m_pressPos.y(); |
|
if (dx * dx + dy * dy >= 25) { |
|
m_selStart = m_pressIndex; |
|
m_selEnd = m_pressIndex; |
|
m_selecting = true; |
|
QRect r = visualRect(QModelIndex(m_pressIndex)); |
|
m_textSel.index = m_pressIndex; |
|
m_textSel.clickPos = m_pressPos - r.topLeft(); |
|
m_textSel.dragPos = m_textSel.clickPos; |
|
m_textSel.active = true; |
|
viewport()->update(); |
|
} |
|
} |
|
if (!m_selecting) { |
|
QModelIndex idx = indexAt(viewport()->mapFrom(this, event->pos())); |
|
if (idx != QModelIndex(m_lastHovered)) { |
|
m_lastHovered = idx; |
|
emit hoveredIndexChanged(idx); |
|
} |
|
} |
|
if (!m_selecting) { |
|
QListView::mouseMoveEvent(event); |
|
return; |
|
} |
|
QModelIndex idx = indexAt(viewport()->mapFrom(this, event->pos())); |
|
if (idx.isValid()) { |
|
m_selEnd = idx; |
|
} |
|
if (m_textSel.active) { |
|
QRect r = visualRect(QModelIndex(m_textSel.index)); |
|
m_textSel.dragPos = event->pos() - r.topLeft(); |
|
} |
|
viewport()->update(); |
|
event->accept(); |
|
} |
|
|
|
void ChatView::mouseReleaseEvent(QMouseEvent *event) { |
|
if (event->button() == Qt::LeftButton && m_selecting) { |
|
m_selecting = false; |
|
viewport()->update(); |
|
} |
|
m_pressIndex = QPersistentModelIndex(); |
|
QListView::mouseReleaseEvent(event); |
|
} |
|
|
|
void ChatView::keyPressEvent(QKeyEvent *event) { |
|
if (event == QKeySequence::Copy) { |
|
copySelection(); |
|
return; |
|
} |
|
QListView::keyPressEvent(event); |
|
} |
|
|
|
void ChatView::copySelection() { |
|
if (!hasSelection()) return; |
|
auto *m = model(); |
|
if (!m) return; |
|
|
|
if (selStart() == selEnd()) { |
|
QModelIndex idx(selStart()); |
|
QString text = idx.data(MsgTextRole).toString(); |
|
if (m_textSel.startChar >= 0 && m_textSel.endChar > m_textSel.startChar) |
|
text = text.mid(m_textSel.startChar, m_textSel.endChar - m_textSel.startChar); |
|
QApplication::clipboard()->clear(QClipboard::Clipboard); |
|
QApplication::clipboard()->setText(text); |
|
} else { |
|
QString r; |
|
int a = selStart().row(), b = selEnd().row(); |
|
if (a > b) qSwap(a, b); |
|
for (int i = a; i <= b; ++i) { |
|
QModelIndex idx = m->index(i, 0); |
|
if (!r.isEmpty()) r += '\n'; |
|
r += '[' + idx.data(MsgTimeRole).toString() + "] "; |
|
r += idx.data(MsgAuthorRole).toString() + ": "; |
|
r += idx.data(MsgTextRole).toString(); |
|
} |
|
QApplication::clipboard()->clear(QClipboard::Clipboard); |
|
QApplication::clipboard()->setText(r); |
|
} |
|
} |
|
|
|
void ChatView::updateScrollDownBtn() { |
|
QScrollBar* sb = verticalScrollBar(); |
|
bool atBottom = sb && (sb->value() >= sb->maximum()); |
|
m_scrollDownBtn->setVisible(!atBottom); |
|
if (!atBottom) { |
|
QRect vr = viewport()->geometry(); |
|
m_scrollDownBtn->move(vr.right() - 44, vr.bottom() - 44); |
|
m_scrollDownBtn->raise(); |
|
} |
|
}
|
|
|