2014-11-16 19:58:43 +08:00
|
|
|
/*
|
2019-06-24 22:01:18 +08:00
|
|
|
Copyright © 2014-2019 by The qTox Project Contributors
|
2015-06-06 09:40:08 +08:00
|
|
|
|
2014-11-16 19:58:43 +08:00
|
|
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
|
|
|
2015-06-06 09:40:08 +08:00
|
|
|
qTox is libre software: you can redistribute it and/or modify
|
2014-11-16 19:58:43 +08:00
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
2015-06-06 09:40:08 +08:00
|
|
|
|
|
|
|
qTox is distributed in the hope that it will be useful,
|
2014-11-16 19:58:43 +08:00
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2015-06-06 09:40:08 +08:00
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
2014-11-16 19:58:43 +08:00
|
|
|
|
2015-06-06 09:40:08 +08:00
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
2014-11-16 19:58:43 +08:00
|
|
|
*/
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
#include "chatlog.h"
|
|
|
|
#include "chatlinecontent.h"
|
2016-07-30 21:15:52 +08:00
|
|
|
#include "chatlinecontentproxy.h"
|
2017-02-26 19:52:45 +08:00
|
|
|
#include "chatmessage.h"
|
2016-07-30 21:15:52 +08:00
|
|
|
#include "content/filetransferwidget.h"
|
2016-12-19 10:26:26 +08:00
|
|
|
#include "src/widget/translator.h"
|
2019-02-19 02:53:43 +08:00
|
|
|
#include "src/widget/style.h"
|
2014-11-16 19:40:44 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <QAction>
|
2014-11-12 21:11:25 +08:00
|
|
|
#include <QApplication>
|
|
|
|
#include <QClipboard>
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <QDebug>
|
2015-01-20 17:44:05 +08:00
|
|
|
#include <QMouseEvent>
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <QScrollBar>
|
2015-02-08 00:37:04 +08:00
|
|
|
#include <QShortcut>
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <QTimer>
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2019-08-12 08:39:57 +08:00
|
|
|
#include <algorithm>
|
|
|
|
#include <cassert>
|
|
|
|
|
2016-07-27 06:19:23 +08:00
|
|
|
/**
|
2016-08-01 16:21:10 +08:00
|
|
|
* @var ChatLog::repNameAfter
|
|
|
|
* @brief repetition interval sender name (sec)
|
|
|
|
*/
|
2016-07-27 06:19:23 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
template <class T>
|
2014-11-12 21:11:25 +08:00
|
|
|
T clamp(T x, T min, T max)
|
|
|
|
{
|
2015-03-21 02:38:10 +08:00
|
|
|
if (x > max)
|
2014-11-12 21:11:25 +08:00
|
|
|
return max;
|
2015-03-21 02:38:10 +08:00
|
|
|
if (x < min)
|
2014-11-12 21:11:25 +08:00
|
|
|
return min;
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
2019-07-01 19:48:10 +08:00
|
|
|
ChatLog::ChatLog(const bool canRemove, QWidget* parent)
|
|
|
|
: QGraphicsView(parent), canRemove(canRemove)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2015-01-15 01:22:42 +08:00
|
|
|
// Create the scene
|
2015-01-25 21:06:06 +08:00
|
|
|
busyScene = new QGraphicsScene(this);
|
2014-11-12 21:11:25 +08:00
|
|
|
scene = new QGraphicsScene(this);
|
2015-02-01 00:39:25 +08:00
|
|
|
scene->setItemIndexMethod(QGraphicsScene::BspTreeIndex);
|
2014-11-12 21:11:25 +08:00
|
|
|
setScene(scene);
|
|
|
|
|
2015-01-15 01:22:42 +08:00
|
|
|
// Cfg.
|
2014-11-12 21:11:25 +08:00
|
|
|
setInteractive(true);
|
2015-02-02 18:01:01 +08:00
|
|
|
setAcceptDrops(false);
|
2014-11-12 21:11:25 +08:00
|
|
|
setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
|
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
setDragMode(QGraphicsView::NoDrag);
|
2015-02-02 18:01:01 +08:00
|
|
|
setViewportUpdateMode(MinimalViewportUpdate);
|
2015-01-04 21:21:05 +08:00
|
|
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
2019-02-23 00:01:43 +08:00
|
|
|
setBackgroundBrush(QBrush(Style::getColor(Style::GroundBase), Qt::SolidPattern));
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-15 01:22:42 +08:00
|
|
|
// The selection rect for multi-line selection
|
2017-02-26 19:52:45 +08:00
|
|
|
selGraphItem = scene->addRect(0, 0, 0, 0, selectionRectColor.darker(120), selectionRectColor);
|
2015-02-02 18:01:01 +08:00
|
|
|
selGraphItem->setZValue(-1.0); // behind all other items
|
2014-12-09 20:17:08 +08:00
|
|
|
|
2015-01-15 01:22:42 +08:00
|
|
|
// copy action (ie. Ctrl+C)
|
2015-02-03 01:11:29 +08:00
|
|
|
copyAction = new QAction(this);
|
2015-01-27 02:32:33 +08:00
|
|
|
copyAction->setIcon(QIcon::fromTheme("edit-copy"));
|
2014-11-12 21:11:25 +08:00
|
|
|
copyAction->setShortcut(QKeySequence::Copy);
|
2015-02-03 01:11:29 +08:00
|
|
|
copyAction->setEnabled(false);
|
2017-02-26 19:52:45 +08:00
|
|
|
connect(copyAction, &QAction::triggered, this, [this]() { copySelectedText(); });
|
2014-11-12 21:11:25 +08:00
|
|
|
addAction(copyAction);
|
2015-01-27 02:32:33 +08:00
|
|
|
|
2015-02-08 00:37:04 +08:00
|
|
|
// Ctrl+Insert shortcut
|
|
|
|
QShortcut* copyCtrlInsShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Insert), this);
|
2017-02-26 19:52:45 +08:00
|
|
|
connect(copyCtrlInsShortcut, &QShortcut::activated, this, [this]() { copySelectedText(); });
|
2015-02-08 00:37:04 +08:00
|
|
|
|
2015-01-27 02:32:33 +08:00
|
|
|
// select all action (ie. Ctrl+A)
|
2015-06-06 03:37:01 +08:00
|
|
|
selectAllAction = new QAction(this);
|
2015-01-27 02:32:33 +08:00
|
|
|
selectAllAction->setIcon(QIcon::fromTheme("edit-select-all"));
|
|
|
|
selectAllAction->setShortcut(QKeySequence::SelectAll);
|
2017-02-26 19:52:45 +08:00
|
|
|
connect(selectAllAction, &QAction::triggered, this, [this]() { selectAll(); });
|
2015-01-27 02:32:33 +08:00
|
|
|
addAction(selectAllAction);
|
2015-01-04 22:18:23 +08:00
|
|
|
|
2015-01-15 01:22:42 +08:00
|
|
|
// This timer is used to scroll the view while the user is
|
|
|
|
// moving the mouse past the top/bottom edge of the widget while selecting.
|
2015-01-04 22:18:23 +08:00
|
|
|
selectionTimer = new QTimer(this);
|
2017-02-26 19:52:45 +08:00
|
|
|
selectionTimer->setInterval(1000 / 30);
|
2015-01-04 22:18:23 +08:00
|
|
|
selectionTimer->setSingleShot(false);
|
|
|
|
selectionTimer->start();
|
|
|
|
connect(selectionTimer, &QTimer::timeout, this, &ChatLog::onSelectionTimerTimeout);
|
2015-01-07 20:05:28 +08:00
|
|
|
|
2015-01-15 01:22:42 +08:00
|
|
|
// Background worker
|
|
|
|
// Updates the layout of all chat-lines after a resize
|
2015-01-07 22:03:02 +08:00
|
|
|
workerTimer = new QTimer(this);
|
|
|
|
workerTimer->setSingleShot(false);
|
2015-02-01 01:37:20 +08:00
|
|
|
workerTimer->setInterval(5);
|
2015-01-25 21:06:06 +08:00
|
|
|
connect(workerTimer, &QTimer::timeout, this, &ChatLog::onWorkerTimeout);
|
2015-02-11 23:32:42 +08:00
|
|
|
|
2017-03-28 23:20:01 +08:00
|
|
|
// This timer is used to detect multiple clicks
|
|
|
|
multiClickTimer = new QTimer(this);
|
|
|
|
multiClickTimer->setSingleShot(true);
|
|
|
|
multiClickTimer->setInterval(QApplication::doubleClickInterval());
|
|
|
|
connect(multiClickTimer, &QTimer::timeout, this, &ChatLog::onMultiClickTimeout);
|
|
|
|
|
2015-02-11 23:32:42 +08:00
|
|
|
// selection
|
|
|
|
connect(this, &ChatLog::selectionChanged, this, [this]() {
|
|
|
|
copyAction->setEnabled(hasTextToBeCopied());
|
|
|
|
copySelectedText(true);
|
|
|
|
});
|
2015-06-06 03:37:01 +08:00
|
|
|
|
|
|
|
retranslateUi();
|
|
|
|
Translator::registerHandler(std::bind(&ChatLog::retranslateUi, this), this);
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2015-02-08 17:11:55 +08:00
|
|
|
ChatLog::~ChatLog()
|
|
|
|
{
|
2015-06-06 03:37:01 +08:00
|
|
|
Translator::unregister(this);
|
|
|
|
|
2015-02-10 00:48:54 +08:00
|
|
|
// Remove chatlines from scene
|
2015-03-21 02:38:10 +08:00
|
|
|
for (ChatLine::Ptr l : lines)
|
2015-02-10 00:48:54 +08:00
|
|
|
l->removeFromScene();
|
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (busyNotification)
|
2015-02-10 00:48:54 +08:00
|
|
|
busyNotification->removeFromScene();
|
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (typingNotification)
|
2015-02-10 00:48:54 +08:00
|
|
|
typingNotification->removeFromScene();
|
2015-02-08 17:11:55 +08:00
|
|
|
}
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
void ChatLog::clearSelection()
|
|
|
|
{
|
2019-09-29 08:07:06 +08:00
|
|
|
if (selectionMode == SelectionMode::None)
|
2015-01-15 02:43:52 +08:00
|
|
|
return;
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
for (int i = selFirstRow; i <= selLastRow; ++i)
|
2014-12-09 20:17:08 +08:00
|
|
|
lines[i]->selectionCleared();
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2014-12-09 20:17:08 +08:00
|
|
|
selFirstRow = -1;
|
2014-11-12 21:11:25 +08:00
|
|
|
selLastRow = -1;
|
2014-12-09 20:17:08 +08:00
|
|
|
selClickedCol = -1;
|
|
|
|
selClickedRow = -1;
|
|
|
|
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionMode = SelectionMode::None;
|
2015-02-11 23:32:42 +08:00
|
|
|
emit selectionChanged();
|
2014-12-10 04:47:25 +08:00
|
|
|
|
|
|
|
updateMultiSelectionRect();
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
QRect ChatLog::getVisibleRect() const
|
|
|
|
{
|
2015-01-15 18:48:41 +08:00
|
|
|
return mapToScene(viewport()->rect()).boundingRect().toRect();
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::updateSceneRect()
|
|
|
|
{
|
2015-01-14 18:27:52 +08:00
|
|
|
setSceneRect(calculateSceneRect());
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2015-01-25 21:06:06 +08:00
|
|
|
void ChatLog::layout(int start, int end, qreal width)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2019-06-20 00:39:16 +08:00
|
|
|
if (lines.empty()) {
|
2015-01-25 21:06:06 +08:00
|
|
|
return;
|
2019-06-20 00:39:16 +08:00
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-06 17:27:03 +08:00
|
|
|
qreal h = 0.0;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-15 01:22:42 +08:00
|
|
|
// Line at start-1 is considered to have the correct position. All following lines are
|
|
|
|
// positioned in respect to this line.
|
2015-03-21 02:38:10 +08:00
|
|
|
if (start - 1 >= 0)
|
2015-02-01 00:49:19 +08:00
|
|
|
h = lines[start - 1]->sceneBoundingRect().bottom() + lineSpacing;
|
2015-01-06 17:27:03 +08:00
|
|
|
|
|
|
|
start = clamp<int>(start, 0, lines.size());
|
|
|
|
end = clamp<int>(end + 1, 0, lines.size());
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
for (int i = start; i < end; ++i) {
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatLine* l = lines[i].get();
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2014-12-10 04:47:25 +08:00
|
|
|
l->layout(width, QPointF(0.0, h));
|
2015-02-01 00:49:19 +08:00
|
|
|
h += l->sceneBoundingRect().height() + lineSpacing;
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::mousePressEvent(QMouseEvent* ev)
|
|
|
|
{
|
|
|
|
QGraphicsView::mousePressEvent(ev);
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (ev->button() == Qt::LeftButton) {
|
2014-11-12 21:11:25 +08:00
|
|
|
clickPos = ev->pos();
|
|
|
|
clearSelection();
|
|
|
|
}
|
2017-03-28 23:20:01 +08:00
|
|
|
|
2018-07-09 22:33:30 +08:00
|
|
|
if (lastClickButton == ev->button()) {
|
|
|
|
// Counts only single clicks and first click of doule click
|
|
|
|
clickCount++;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
clickCount = 1; // restarting counter
|
|
|
|
lastClickButton = ev->button();
|
|
|
|
}
|
2017-03-28 23:20:01 +08:00
|
|
|
lastClickPos = ev->pos();
|
|
|
|
|
|
|
|
// Triggers on odd click counts
|
|
|
|
handleMultiClickEvent();
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::mouseReleaseEvent(QMouseEvent* ev)
|
|
|
|
{
|
|
|
|
QGraphicsView::mouseReleaseEvent(ev);
|
2015-01-04 21:21:05 +08:00
|
|
|
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionScrollDir = AutoScrollDirection::NoDirection;
|
2017-03-28 23:20:01 +08:00
|
|
|
|
|
|
|
multiClickTimer->start();
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::mouseMoveEvent(QMouseEvent* ev)
|
|
|
|
{
|
|
|
|
QGraphicsView::mouseMoveEvent(ev);
|
|
|
|
|
|
|
|
QPointF scenePos = mapToScene(ev->pos());
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (ev->buttons() & Qt::LeftButton) {
|
|
|
|
// autoscroll
|
2015-03-21 02:38:10 +08:00
|
|
|
if (ev->pos().y() < 0)
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionScrollDir = AutoScrollDirection::Up;
|
2015-03-21 02:38:10 +08:00
|
|
|
else if (ev->pos().y() > height())
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionScrollDir = AutoScrollDirection::Down;
|
2015-01-04 22:18:23 +08:00
|
|
|
else
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionScrollDir = AutoScrollDirection::NoDirection;
|
2015-01-04 22:18:23 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
// select
|
2019-09-29 08:07:06 +08:00
|
|
|
if (selectionMode == SelectionMode::None
|
2017-02-26 19:52:45 +08:00
|
|
|
&& (clickPos - ev->pos()).manhattanLength() > QApplication::startDragDistance()) {
|
2014-11-12 21:11:25 +08:00
|
|
|
QPointF sceneClickPos = mapToScene(clickPos.toPoint());
|
2015-01-15 18:48:41 +08:00
|
|
|
ChatLine::Ptr line = findLineByPosY(scenePos.y());
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
ChatLineContent* content = getContentFromPos(sceneClickPos);
|
2017-02-26 19:52:45 +08:00
|
|
|
if (content) {
|
2014-12-09 20:17:08 +08:00
|
|
|
selClickedRow = content->getRow();
|
|
|
|
selClickedCol = content->getColumn();
|
|
|
|
selFirstRow = content->getRow();
|
|
|
|
selLastRow = content->getRow();
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
content->selectionStarted(sceneClickPos);
|
|
|
|
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionMode = SelectionMode::Precise;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
// ungrab mouse grabber
|
2015-03-21 02:38:10 +08:00
|
|
|
if (scene->mouseGrabberItem())
|
2014-11-12 21:11:25 +08:00
|
|
|
scene->mouseGrabberItem()->ungrabMouse();
|
2017-02-26 19:52:45 +08:00
|
|
|
} else if (line.get()) {
|
2015-01-20 17:31:50 +08:00
|
|
|
selClickedRow = line->getRow();
|
2015-01-15 02:43:52 +08:00
|
|
|
selFirstRow = selClickedRow;
|
|
|
|
selLastRow = selClickedRow;
|
|
|
|
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionMode = SelectionMode::Multi;
|
2015-01-15 02:43:52 +08:00
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2019-09-29 08:07:06 +08:00
|
|
|
if (selectionMode != SelectionMode::None) {
|
2015-01-02 18:25:07 +08:00
|
|
|
ChatLineContent* content = getContentFromPos(scenePos);
|
2015-01-15 18:48:41 +08:00
|
|
|
ChatLine::Ptr line = findLineByPosY(scenePos.y());
|
2015-01-15 02:43:52 +08:00
|
|
|
|
|
|
|
int row;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (content) {
|
2015-01-15 02:43:52 +08:00
|
|
|
row = content->getRow();
|
2015-01-02 18:25:07 +08:00
|
|
|
int col = content->getColumn();
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (row == selClickedRow && col == selClickedCol) {
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionMode = SelectionMode::Precise;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
content->selectionMouseMove(scenePos);
|
|
|
|
selGraphItem->hide();
|
2017-02-26 19:52:45 +08:00
|
|
|
} else if (col != selClickedCol) {
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionMode = SelectionMode::Multi;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
lines[selClickedRow]->selectionCleared();
|
2015-01-15 02:43:52 +08:00
|
|
|
}
|
2017-02-26 19:52:45 +08:00
|
|
|
} else if (line.get()) {
|
2015-01-20 17:31:50 +08:00
|
|
|
row = line->getRow();
|
2015-01-15 02:43:52 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (row != selClickedRow) {
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionMode = SelectionMode::Multi;
|
2015-01-15 02:43:52 +08:00
|
|
|
lines[selClickedRow]->selectionCleared();
|
2015-01-02 18:25:07 +08:00
|
|
|
}
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
2015-02-15 19:15:46 +08:00
|
|
|
return;
|
2015-08-30 20:00:54 +08:00
|
|
|
}
|
2015-01-15 02:43:52 +08:00
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (row >= selClickedRow)
|
2015-01-15 02:43:52 +08:00
|
|
|
selLastRow = row;
|
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (row <= selClickedRow)
|
2015-01-15 02:43:52 +08:00
|
|
|
selFirstRow = row;
|
|
|
|
|
|
|
|
updateMultiSelectionRect();
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
2015-02-10 21:36:53 +08:00
|
|
|
|
2015-02-11 23:32:42 +08:00
|
|
|
emit selectionChanged();
|
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
// Much faster than QGraphicsScene::itemAt()!
|
2014-11-12 21:11:25 +08:00
|
|
|
ChatLineContent* ChatLog::getContentFromPos(QPointF scenePos) const
|
|
|
|
{
|
2019-06-20 00:39:16 +08:00
|
|
|
if (lines.empty()) {
|
2015-01-07 00:47:57 +08:00
|
|
|
return nullptr;
|
2019-06-20 00:39:16 +08:00
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
auto itr =
|
|
|
|
std::lower_bound(lines.cbegin(), lines.cend(), scenePos.y(), ChatLine::lessThanBSRectBottom);
|
2015-01-07 00:47:57 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
// find content
|
2015-03-21 02:38:10 +08:00
|
|
|
if (itr != lines.cend() && (*itr)->sceneBoundingRect().contains(scenePos))
|
2015-01-15 18:48:41 +08:00
|
|
|
return (*itr)->getContent(scenePos);
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-01-14 06:59:38 +08:00
|
|
|
bool ChatLog::isOverSelection(QPointF scenePos) const
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2019-09-29 08:07:06 +08:00
|
|
|
if (selectionMode == SelectionMode::Precise) {
|
2014-12-09 20:17:08 +08:00
|
|
|
ChatLineContent* content = getContentFromPos(scenePos);
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (content)
|
2014-12-09 20:17:08 +08:00
|
|
|
return content->isOverSelection(scenePos);
|
2019-09-29 08:07:06 +08:00
|
|
|
} else if (selectionMode == SelectionMode::Multi) {
|
2015-03-21 02:38:10 +08:00
|
|
|
if (selGraphItem->rect().contains(scenePos))
|
2014-12-09 20:17:08 +08:00
|
|
|
return true;
|
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-01-14 06:59:38 +08:00
|
|
|
qreal ChatLog::useableWidth() const
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2014-12-10 04:47:25 +08:00
|
|
|
return width() - verticalScrollBar()->sizeHint().width() - margins.right() - margins.left();
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2015-01-05 21:06:14 +08:00
|
|
|
void ChatLog::reposition(int start, int end, qreal deltaY)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2015-03-21 02:38:10 +08:00
|
|
|
if (lines.isEmpty())
|
2014-11-12 21:11:25 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
start = clamp<int>(start, 0, lines.size() - 1);
|
|
|
|
end = clamp<int>(end + 1, 0, lines.size());
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
for (int i = start; i < end; ++i) {
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatLine* l = lines[i].get();
|
2015-01-05 21:06:14 +08:00
|
|
|
l->moveBy(deltaY);
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-05 02:28:27 +08:00
|
|
|
void ChatLog::insertChatlineAtBottom(ChatLine::Ptr l)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2019-07-01 19:27:58 +08:00
|
|
|
numRemove = 0;
|
2015-03-21 02:38:10 +08:00
|
|
|
if (!l.get())
|
2015-01-05 01:21:35 +08:00
|
|
|
return;
|
|
|
|
|
2015-01-14 06:59:38 +08:00
|
|
|
bool stickToBtm = stickToBottom();
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
// insert
|
2015-01-20 17:31:50 +08:00
|
|
|
l->setRow(lines.size());
|
2015-01-14 06:59:38 +08:00
|
|
|
l->addToScene(scene);
|
2014-11-12 21:11:25 +08:00
|
|
|
lines.append(l);
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
// partial refresh
|
2015-01-20 17:31:50 +08:00
|
|
|
layout(lines.last()->getRow(), lines.size(), useableWidth());
|
2014-11-12 21:11:25 +08:00
|
|
|
updateSceneRect();
|
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (stickToBtm)
|
2014-11-12 21:11:25 +08:00
|
|
|
scrollToBottom();
|
|
|
|
|
|
|
|
checkVisibility();
|
2015-01-17 18:35:09 +08:00
|
|
|
updateTypingNotification();
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2019-05-20 04:52:36 +08:00
|
|
|
void ChatLog::insertChatlineAtBottom(const QList<ChatLine::Ptr>& newLines)
|
|
|
|
{
|
2019-07-01 19:27:58 +08:00
|
|
|
numRemove = 0;
|
2019-05-20 04:52:36 +08:00
|
|
|
if (newLines.isEmpty())
|
|
|
|
return;
|
|
|
|
|
2019-07-01 19:48:10 +08:00
|
|
|
if (canRemove && lines.size() + DEF_NUM_MSG_TO_LOAD >= maxMessages) {
|
2019-07-01 19:27:58 +08:00
|
|
|
removeFirsts(DEF_NUM_MSG_TO_LOAD);
|
|
|
|
}
|
|
|
|
|
2019-05-20 04:52:36 +08:00
|
|
|
for (ChatLine::Ptr l : newLines) {
|
|
|
|
l->setRow(lines.size());
|
|
|
|
l->addToScene(scene);
|
|
|
|
l->visibilityChanged(false);
|
|
|
|
lines.append(l);
|
|
|
|
}
|
|
|
|
|
|
|
|
layout(lines.last()->getRow(), lines.size(), useableWidth());
|
2019-07-01 19:27:58 +08:00
|
|
|
|
2020-03-01 10:12:40 +08:00
|
|
|
// redo layout only when scrolled down
|
|
|
|
if(stickToBottom()) {
|
|
|
|
startResizeWorker(true);
|
2019-07-01 19:27:58 +08:00
|
|
|
}
|
2019-05-20 04:52:36 +08:00
|
|
|
}
|
|
|
|
|
2015-01-05 02:28:27 +08:00
|
|
|
void ChatLog::insertChatlineOnTop(ChatLine::Ptr l)
|
|
|
|
{
|
2019-07-01 19:27:58 +08:00
|
|
|
numRemove = 0;
|
2015-03-21 02:38:10 +08:00
|
|
|
if (!l.get())
|
2015-01-05 02:28:27 +08:00
|
|
|
return;
|
|
|
|
|
2018-04-24 07:51:26 +08:00
|
|
|
insertChatlinesOnTop(QList<ChatLine::Ptr>() << l);
|
2015-01-05 02:28:27 +08:00
|
|
|
}
|
|
|
|
|
2018-04-24 07:51:26 +08:00
|
|
|
void ChatLog::insertChatlinesOnTop(const QList<ChatLine::Ptr>& newLines)
|
2015-01-05 17:51:01 +08:00
|
|
|
{
|
2019-07-01 19:27:58 +08:00
|
|
|
numRemove = 0;
|
2015-03-21 02:38:10 +08:00
|
|
|
if (newLines.isEmpty())
|
2015-01-05 17:51:01 +08:00
|
|
|
return;
|
|
|
|
|
2015-02-10 21:30:49 +08:00
|
|
|
QGraphicsScene::ItemIndexMethod oldIndexMeth = scene->itemIndexMethod();
|
|
|
|
scene->setItemIndexMethod(QGraphicsScene::NoIndex);
|
|
|
|
|
|
|
|
// alloc space for old and new lines
|
|
|
|
QVector<ChatLine::Ptr> combLines;
|
|
|
|
combLines.reserve(newLines.size() + lines.size());
|
2015-01-05 17:51:01 +08:00
|
|
|
|
2015-02-10 21:30:49 +08:00
|
|
|
// add the new lines
|
|
|
|
int i = 0;
|
2017-02-26 19:52:45 +08:00
|
|
|
for (ChatLine::Ptr l : newLines) {
|
2015-01-05 17:51:01 +08:00
|
|
|
l->addToScene(scene);
|
2015-02-01 00:39:25 +08:00
|
|
|
l->visibilityChanged(false);
|
2015-02-10 21:30:49 +08:00
|
|
|
l->setRow(i++);
|
|
|
|
combLines.push_back(l);
|
|
|
|
}
|
|
|
|
|
2019-07-01 19:48:10 +08:00
|
|
|
if (canRemove && lines.size() + DEF_NUM_MSG_TO_LOAD >= maxMessages) {
|
2019-07-01 19:27:58 +08:00
|
|
|
removeLasts(DEF_NUM_MSG_TO_LOAD);
|
|
|
|
}
|
|
|
|
|
2015-02-10 21:30:49 +08:00
|
|
|
// add the old lines
|
2017-02-26 19:52:45 +08:00
|
|
|
for (ChatLine::Ptr l : lines) {
|
2015-02-10 21:30:49 +08:00
|
|
|
l->setRow(i++);
|
|
|
|
combLines.push_back(l);
|
2015-01-05 17:51:01 +08:00
|
|
|
}
|
|
|
|
|
2015-02-10 21:30:49 +08:00
|
|
|
lines = combLines;
|
|
|
|
|
2019-09-29 08:01:48 +08:00
|
|
|
moveSelectionRectDownIfSelected(newLines.size());
|
2019-08-12 08:39:57 +08:00
|
|
|
|
2015-02-10 21:30:49 +08:00
|
|
|
scene->setItemIndexMethod(oldIndexMeth);
|
|
|
|
|
2015-01-25 21:06:06 +08:00
|
|
|
// redo layout
|
2019-07-01 19:27:58 +08:00
|
|
|
if (visibleLines.size() > 1) {
|
2020-03-01 10:12:40 +08:00
|
|
|
startResizeWorker(stickToBottom(), visibleLines[1]);
|
2019-07-01 19:27:58 +08:00
|
|
|
} else {
|
2020-03-01 10:12:40 +08:00
|
|
|
startResizeWorker(stickToBottom());
|
2019-07-01 19:27:58 +08:00
|
|
|
}
|
|
|
|
|
2015-01-05 17:51:01 +08:00
|
|
|
}
|
|
|
|
|
2015-01-14 06:59:38 +08:00
|
|
|
bool ChatLog::stickToBottom() const
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
|
|
|
return verticalScrollBar()->value() == verticalScrollBar()->maximum();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::scrollToBottom()
|
|
|
|
{
|
2015-01-14 06:59:38 +08:00
|
|
|
updateSceneRect();
|
2014-11-12 21:11:25 +08:00
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
|
|
|
}
|
|
|
|
|
2020-03-01 10:12:40 +08:00
|
|
|
void ChatLog::startResizeWorker(bool stick, ChatLine::Ptr anchorLine)
|
2015-01-25 21:06:06 +08:00
|
|
|
{
|
2019-06-20 00:39:16 +08:00
|
|
|
if (lines.empty()) {
|
2019-07-01 19:27:58 +08:00
|
|
|
isScroll = true;
|
2015-01-27 02:15:29 +08:00
|
|
|
return;
|
2019-06-20 00:39:16 +08:00
|
|
|
}
|
2015-01-27 02:15:29 +08:00
|
|
|
|
2015-01-25 21:06:06 +08:00
|
|
|
// (re)start the worker
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!workerTimer->isActive()) {
|
2015-01-25 21:06:06 +08:00
|
|
|
// these values must not be reevaluated while the worker is running
|
2020-03-01 10:12:40 +08:00
|
|
|
workerStb = stick;
|
|
|
|
if (stick) {
|
|
|
|
workerAnchorLine = ChatLine::Ptr();
|
2019-07-01 19:27:58 +08:00
|
|
|
} else {
|
2020-03-01 10:12:40 +08:00
|
|
|
workerAnchorLine = anchorLine;
|
2019-07-01 19:27:58 +08:00
|
|
|
}
|
2015-01-25 21:06:06 +08:00
|
|
|
}
|
|
|
|
|
2015-03-08 13:31:21 +08:00
|
|
|
// switch to busy scene displaying the busy notification if there is a lot
|
|
|
|
// of text to be resized
|
|
|
|
int txt = 0;
|
2017-02-26 19:52:45 +08:00
|
|
|
for (ChatLine::Ptr line : lines) {
|
2015-03-17 01:56:26 +08:00
|
|
|
if (txt > 500000)
|
2015-03-08 13:31:21 +08:00
|
|
|
break;
|
2015-03-17 01:56:26 +08:00
|
|
|
for (ChatLineContent* content : line->content)
|
2015-03-08 13:31:21 +08:00
|
|
|
txt += content->getText().size();
|
|
|
|
}
|
2015-03-17 01:56:26 +08:00
|
|
|
if (txt > 500000)
|
2015-03-08 13:31:21 +08:00
|
|
|
setScene(busyScene);
|
|
|
|
|
2015-01-25 21:06:06 +08:00
|
|
|
workerLastIndex = 0;
|
|
|
|
workerTimer->start();
|
|
|
|
|
|
|
|
verticalScrollBar()->hide();
|
|
|
|
}
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
void ChatLog::mouseDoubleClickEvent(QMouseEvent* ev)
|
2015-01-19 22:19:54 +08:00
|
|
|
{
|
|
|
|
QPointF scenePos = mapToScene(ev->pos());
|
|
|
|
ChatLineContent* content = getContentFromPos(scenePos);
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (content) {
|
2015-01-19 22:19:54 +08:00
|
|
|
content->selectionDoubleClick(scenePos);
|
|
|
|
selClickedCol = content->getColumn();
|
|
|
|
selClickedRow = content->getRow();
|
|
|
|
selFirstRow = content->getRow();
|
|
|
|
selLastRow = content->getRow();
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionMode = SelectionMode::Precise;
|
2015-02-11 23:32:42 +08:00
|
|
|
|
|
|
|
emit selectionChanged();
|
2015-01-19 22:19:54 +08:00
|
|
|
}
|
2017-03-28 23:20:01 +08:00
|
|
|
|
2018-07-09 22:33:30 +08:00
|
|
|
if (lastClickButton == ev->button()) {
|
|
|
|
// Counts the second click of double click
|
|
|
|
clickCount++;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
clickCount = 1; // restarting counter
|
|
|
|
lastClickButton = ev->button();
|
|
|
|
}
|
2017-03-28 23:20:01 +08:00
|
|
|
lastClickPos = ev->pos();
|
|
|
|
|
|
|
|
// Triggers on even click counts
|
|
|
|
handleMultiClickEvent();
|
2015-01-19 22:19:54 +08:00
|
|
|
}
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
QString ChatLog::getSelectedText() const
|
|
|
|
{
|
2019-09-29 08:07:06 +08:00
|
|
|
if (selectionMode == SelectionMode::Precise) {
|
2014-12-09 20:17:08 +08:00
|
|
|
return lines[selClickedRow]->content[selClickedCol]->getSelectedText();
|
2019-09-29 08:07:06 +08:00
|
|
|
} else if (selectionMode == SelectionMode::Multi) {
|
2014-12-09 20:17:08 +08:00
|
|
|
// build a nicely formatted message
|
|
|
|
QString out;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
for (int i = selFirstRow; i <= selLastRow; ++i) {
|
2015-03-21 02:38:10 +08:00
|
|
|
if (lines[i]->content[1]->getText().isEmpty())
|
2015-01-15 03:31:40 +08:00
|
|
|
continue;
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
QString timestamp = lines[i]->content[2]->getText().isEmpty()
|
|
|
|
? tr("pending")
|
|
|
|
: lines[i]->content[2]->getText();
|
2015-02-11 23:37:02 +08:00
|
|
|
QString author = lines[i]->content[0]->getText();
|
|
|
|
QString msg = lines[i]->content[1]->getText();
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
out +=
|
|
|
|
QString(out.isEmpty() ? "[%2] %1: %3" : "\n[%2] %1: %3").arg(author, timestamp, msg);
|
2014-12-09 20:17:08 +08:00
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2014-12-09 20:17:08 +08:00
|
|
|
return out;
|
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2014-12-09 20:17:08 +08:00
|
|
|
return QString();
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2014-12-14 04:11:03 +08:00
|
|
|
bool ChatLog::isEmpty() const
|
|
|
|
{
|
|
|
|
return lines.isEmpty();
|
|
|
|
}
|
|
|
|
|
2015-01-04 21:21:05 +08:00
|
|
|
bool ChatLog::hasTextToBeCopied() const
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2019-09-29 08:07:06 +08:00
|
|
|
return selectionMode != SelectionMode::None;
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2015-01-10 18:57:46 +08:00
|
|
|
ChatLine::Ptr ChatLog::getTypingNotification() const
|
|
|
|
{
|
|
|
|
return typingNotification;
|
|
|
|
}
|
|
|
|
|
2015-01-25 21:34:36 +08:00
|
|
|
QVector<ChatLine::Ptr> ChatLog::getLines()
|
|
|
|
{
|
|
|
|
return lines;
|
|
|
|
}
|
|
|
|
|
2015-06-24 02:58:30 +08:00
|
|
|
ChatLine::Ptr ChatLog::getLatestLine() const
|
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!lines.empty()) {
|
2015-06-24 02:58:30 +08:00
|
|
|
return lines.last();
|
|
|
|
}
|
|
|
|
return nullptr;
|
2018-06-25 02:11:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
ChatLine::Ptr ChatLog::getFirstLine() const
|
|
|
|
{
|
|
|
|
if (!lines.empty()) {
|
|
|
|
return lines.first();
|
|
|
|
}
|
|
|
|
return nullptr;
|
2015-06-24 02:58:30 +08:00
|
|
|
}
|
|
|
|
|
2016-12-07 08:59:07 +08:00
|
|
|
/**
|
|
|
|
* @brief Finds the chat line object at a position on screen
|
|
|
|
* @param pos Position on screen in global coordinates
|
|
|
|
* @sa getContentFromPos()
|
|
|
|
*/
|
2017-02-26 19:52:45 +08:00
|
|
|
ChatLineContent* ChatLog::getContentFromGlobalPos(QPoint pos) const
|
2016-12-07 08:59:07 +08:00
|
|
|
{
|
|
|
|
return getContentFromPos(mapToScene(mapFromGlobal(pos)));
|
|
|
|
}
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
void ChatLog::clear()
|
|
|
|
{
|
|
|
|
clearSelection();
|
|
|
|
|
2016-07-30 21:15:52 +08:00
|
|
|
QVector<ChatLine::Ptr> savedLines;
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
for (ChatLine::Ptr l : lines) {
|
2016-07-30 21:15:52 +08:00
|
|
|
if (isActiveFileTransfer(l))
|
|
|
|
savedLines.push_back(l);
|
|
|
|
else
|
|
|
|
l->removeFromScene();
|
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
lines.clear();
|
2015-01-04 20:29:14 +08:00
|
|
|
visibleLines.clear();
|
2016-07-30 21:15:52 +08:00
|
|
|
for (ChatLine::Ptr l : savedLines)
|
|
|
|
insertChatlineAtBottom(l);
|
2015-01-04 20:29:14 +08:00
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
updateSceneRect();
|
|
|
|
}
|
|
|
|
|
2015-02-08 17:32:52 +08:00
|
|
|
void ChatLog::copySelectedText(bool toSelectionBuffer) const
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
|
|
|
QString text = getSelectedText();
|
|
|
|
QClipboard* clipboard = QApplication::clipboard();
|
2015-02-08 00:39:56 +08:00
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (clipboard && !text.isNull())
|
2015-02-08 17:32:52 +08:00
|
|
|
clipboard->setText(text, toSelectionBuffer ? QClipboard::Selection : QClipboard::Clipboard);
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2015-01-25 21:06:06 +08:00
|
|
|
void ChatLog::setBusyNotification(ChatLine::Ptr notification)
|
|
|
|
{
|
2015-03-21 02:38:10 +08:00
|
|
|
if (!notification.get())
|
2015-01-25 21:06:06 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
busyNotification = notification;
|
|
|
|
busyNotification->addToScene(busyScene);
|
|
|
|
busyNotification->visibilityChanged(true);
|
|
|
|
}
|
|
|
|
|
2015-01-10 18:57:46 +08:00
|
|
|
void ChatLog::setTypingNotification(ChatLine::Ptr notification)
|
|
|
|
{
|
|
|
|
typingNotification = notification;
|
|
|
|
typingNotification->visibilityChanged(true);
|
|
|
|
typingNotification->setVisible(false);
|
|
|
|
typingNotification->addToScene(scene);
|
|
|
|
updateTypingNotification();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::setTypingNotificationVisible(bool visible)
|
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (typingNotification.get()) {
|
2015-01-10 18:57:46 +08:00
|
|
|
typingNotification->setVisible(visible);
|
|
|
|
updateTypingNotification();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-25 21:06:06 +08:00
|
|
|
void ChatLog::scrollToLine(ChatLine::Ptr line)
|
|
|
|
{
|
2015-03-21 02:38:10 +08:00
|
|
|
if (!line.get())
|
2015-01-25 21:06:06 +08:00
|
|
|
return;
|
|
|
|
|
2019-07-01 19:27:58 +08:00
|
|
|
if (workerTimer->isActive()) {
|
|
|
|
workerAnchorLine = line;
|
|
|
|
workerStb = false;
|
|
|
|
} else {
|
|
|
|
updateSceneRect();
|
2019-08-22 20:11:18 +08:00
|
|
|
verticalScrollBar()->setValue(line->sceneBoundingRect().top());
|
2019-07-01 19:27:58 +08:00
|
|
|
}
|
2015-01-25 21:06:06 +08:00
|
|
|
}
|
|
|
|
|
2015-01-27 02:32:33 +08:00
|
|
|
void ChatLog::selectAll()
|
|
|
|
{
|
2019-06-20 00:39:16 +08:00
|
|
|
if (lines.empty()) {
|
2015-01-27 02:32:33 +08:00
|
|
|
return;
|
2019-06-20 00:39:16 +08:00
|
|
|
}
|
2015-01-27 02:32:33 +08:00
|
|
|
|
|
|
|
clearSelection();
|
|
|
|
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionMode = SelectionMode::Multi;
|
2015-01-27 02:32:33 +08:00
|
|
|
selFirstRow = 0;
|
2017-02-26 19:52:45 +08:00
|
|
|
selLastRow = lines.size() - 1;
|
2015-01-27 02:32:33 +08:00
|
|
|
|
2015-02-11 23:32:42 +08:00
|
|
|
emit selectionChanged();
|
2015-01-27 02:32:33 +08:00
|
|
|
updateMultiSelectionRect();
|
|
|
|
}
|
|
|
|
|
2017-02-05 11:10:27 +08:00
|
|
|
void ChatLog::fontChanged(const QFont& font)
|
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
for (ChatLine::Ptr l : lines) {
|
2017-02-05 11:10:27 +08:00
|
|
|
l->fontChanged(font);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-21 21:53:31 +08:00
|
|
|
void ChatLog::reloadTheme()
|
|
|
|
{
|
2019-02-23 00:01:43 +08:00
|
|
|
setBackgroundBrush(QBrush(Style::getColor(Style::GroundBase), Qt::SolidPattern));
|
2019-03-02 07:58:58 +08:00
|
|
|
selectionRectColor = Style::getColor(Style::SelectText);
|
|
|
|
selGraphItem->setBrush(QBrush(selectionRectColor));
|
|
|
|
selGraphItem->setPen(QPen(selectionRectColor.darker(120)));
|
2019-02-21 21:53:31 +08:00
|
|
|
|
|
|
|
for (ChatLine::Ptr l : lines) {
|
|
|
|
l->reloadTheme();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-30 21:30:31 +08:00
|
|
|
void ChatLog::removeFirsts(const int num)
|
|
|
|
{
|
|
|
|
if (lines.size() > num) {
|
|
|
|
lines.erase(lines.begin(), lines.begin()+num);
|
2019-07-01 19:27:58 +08:00
|
|
|
numRemove = num;
|
2019-05-30 21:30:31 +08:00
|
|
|
} else {
|
|
|
|
lines.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < lines.size(); ++i) {
|
|
|
|
lines[i]->setRow(i);
|
|
|
|
}
|
2019-08-12 08:39:57 +08:00
|
|
|
|
2019-09-29 08:01:48 +08:00
|
|
|
moveSelectionRectUpIfSelected(num);
|
2019-05-30 21:30:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::removeLasts(const int num)
|
|
|
|
{
|
|
|
|
if (lines.size() > num) {
|
|
|
|
lines.erase(lines.end()-num, lines.end());
|
2019-07-01 19:27:58 +08:00
|
|
|
numRemove = num;
|
2019-05-30 21:30:31 +08:00
|
|
|
} else {
|
|
|
|
lines.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-01 19:27:58 +08:00
|
|
|
void ChatLog::setScroll(const bool scroll)
|
|
|
|
{
|
|
|
|
isScroll = scroll;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ChatLog::getNumRemove() const
|
|
|
|
{
|
|
|
|
return numRemove;
|
|
|
|
}
|
|
|
|
|
2015-02-15 18:43:01 +08:00
|
|
|
void ChatLog::forceRelayout()
|
|
|
|
{
|
2020-03-01 10:12:40 +08:00
|
|
|
startResizeWorker(stickToBottom());
|
2015-02-15 18:43:01 +08:00
|
|
|
}
|
|
|
|
|
2019-05-27 04:22:15 +08:00
|
|
|
void ChatLog::checkVisibility(bool causedWheelEvent)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2019-06-20 00:39:16 +08:00
|
|
|
if (lines.empty()) {
|
2015-01-04 04:19:52 +08:00
|
|
|
return;
|
2019-06-20 00:39:16 +08:00
|
|
|
}
|
2015-01-04 04:19:52 +08:00
|
|
|
|
2015-01-14 17:34:52 +08:00
|
|
|
// find first visible line
|
2017-02-26 19:52:45 +08:00
|
|
|
auto lowerBound = std::lower_bound(lines.cbegin(), lines.cend(), getVisibleRect().top(),
|
|
|
|
ChatLine::lessThanBSRectBottom);
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-14 17:34:52 +08:00
|
|
|
// find last visible line
|
2017-02-26 19:52:45 +08:00
|
|
|
auto upperBound = std::lower_bound(lowerBound, lines.cend(), getVisibleRect().bottom(),
|
|
|
|
ChatLine::lessThanBSRectTop);
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2019-10-13 06:08:56 +08:00
|
|
|
const ChatLine::Ptr lastLineBeforeVisible = lowerBound == lines.cbegin()
|
|
|
|
? ChatLine::Ptr()
|
|
|
|
: *std::prev(lowerBound);
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
// set visibilty
|
2015-01-04 20:29:14 +08:00
|
|
|
QList<ChatLine::Ptr> newVisibleLines;
|
2017-02-26 19:52:45 +08:00
|
|
|
for (auto itr = lowerBound; itr != upperBound; ++itr) {
|
2014-11-12 21:11:25 +08:00
|
|
|
newVisibleLines.append(*itr);
|
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (!visibleLines.contains(*itr))
|
2014-11-12 21:11:25 +08:00
|
|
|
(*itr)->visibilityChanged(true);
|
|
|
|
|
|
|
|
visibleLines.removeOne(*itr);
|
|
|
|
}
|
|
|
|
|
2015-01-14 17:34:52 +08:00
|
|
|
// these lines are no longer visible
|
2015-03-21 02:38:10 +08:00
|
|
|
for (ChatLine::Ptr line : visibleLines)
|
2014-11-12 21:11:25 +08:00
|
|
|
line->visibilityChanged(false);
|
|
|
|
|
|
|
|
visibleLines = newVisibleLines;
|
|
|
|
|
2015-01-05 17:51:01 +08:00
|
|
|
// enforce order
|
2015-01-15 18:48:41 +08:00
|
|
|
std::sort(visibleLines.begin(), visibleLines.end(), ChatLine::lessThanRowIndex);
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
// if (!visibleLines.empty())
|
|
|
|
// qDebug() << "visible from " << visibleLines.first()->getRow() << "to " <<
|
|
|
|
// visibleLines.last()->getRow() << " total " << visibleLines.size();
|
2019-01-28 00:36:20 +08:00
|
|
|
|
|
|
|
if (!visibleLines.isEmpty()) {
|
2019-10-13 06:08:56 +08:00
|
|
|
emit firstVisibleLineChanged(lastLineBeforeVisible, visibleLines.at(0));
|
2019-01-28 00:36:20 +08:00
|
|
|
}
|
2019-05-19 22:30:44 +08:00
|
|
|
|
2019-05-27 04:22:15 +08:00
|
|
|
if (causedWheelEvent) {
|
2019-05-20 04:52:36 +08:00
|
|
|
if (lowerBound != lines.cend() && lowerBound->get()->row == 0) {
|
|
|
|
emit loadHistoryLower();
|
2019-05-27 04:22:15 +08:00
|
|
|
} else if (upperBound == lines.cend()) {
|
2019-05-20 04:52:36 +08:00
|
|
|
emit loadHistoryUpper();
|
|
|
|
}
|
2019-05-19 22:30:44 +08:00
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::scrollContentsBy(int dx, int dy)
|
|
|
|
{
|
|
|
|
QGraphicsView::scrollContentsBy(dx, dy);
|
2019-05-27 04:22:15 +08:00
|
|
|
checkVisibility();
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::resizeEvent(QResizeEvent* ev)
|
|
|
|
{
|
2015-02-04 00:28:42 +08:00
|
|
|
bool stb = stickToBottom();
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (ev->size().width() != ev->oldSize().width()) {
|
2020-03-01 10:12:40 +08:00
|
|
|
startResizeWorker(stb);
|
2015-02-04 00:28:42 +08:00
|
|
|
stb = false; // let the resize worker handle it
|
|
|
|
}
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
QGraphicsView::resizeEvent(ev);
|
2015-01-27 02:01:18 +08:00
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (stb)
|
2015-02-04 00:28:42 +08:00
|
|
|
scrollToBottom();
|
|
|
|
|
2015-01-27 02:01:18 +08:00
|
|
|
updateBusyNotification();
|
2014-12-10 04:47:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::updateMultiSelectionRect()
|
|
|
|
{
|
2019-09-29 08:07:06 +08:00
|
|
|
if (selectionMode == SelectionMode::Multi && selFirstRow >= 0 && selLastRow >= 0) {
|
2014-12-10 04:47:25 +08:00
|
|
|
QRectF selBBox;
|
2015-02-01 00:49:19 +08:00
|
|
|
selBBox = selBBox.united(lines[selFirstRow]->sceneBoundingRect());
|
|
|
|
selBBox = selBBox.united(lines[selLastRow]->sceneBoundingRect());
|
2014-12-10 04:47:25 +08:00
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (selGraphItem->rect() != selBBox)
|
2015-02-02 18:01:01 +08:00
|
|
|
scene->invalidate(selGraphItem->rect());
|
2015-02-01 02:19:13 +08:00
|
|
|
|
2014-12-10 04:47:25 +08:00
|
|
|
selGraphItem->setRect(selBBox);
|
|
|
|
selGraphItem->show();
|
2017-02-26 19:52:45 +08:00
|
|
|
} else {
|
2014-12-10 04:47:25 +08:00
|
|
|
selGraphItem->hide();
|
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
2015-01-04 22:18:23 +08:00
|
|
|
|
2015-01-10 18:57:46 +08:00
|
|
|
void ChatLog::updateTypingNotification()
|
|
|
|
{
|
|
|
|
ChatLine* notification = typingNotification.get();
|
2015-03-21 02:38:10 +08:00
|
|
|
if (!notification)
|
2015-01-10 18:57:46 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
qreal posY = 0.0;
|
|
|
|
|
2019-06-20 00:39:16 +08:00
|
|
|
if (!lines.empty()) {
|
2015-02-01 00:49:19 +08:00
|
|
|
posY = lines.last()->sceneBoundingRect().bottom() + lineSpacing;
|
2019-06-20 00:39:16 +08:00
|
|
|
}
|
2015-01-10 18:57:46 +08:00
|
|
|
|
|
|
|
notification->layout(useableWidth(), QPointF(0.0, posY));
|
|
|
|
}
|
|
|
|
|
2015-01-25 21:06:06 +08:00
|
|
|
void ChatLog::updateBusyNotification()
|
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
if (busyNotification.get()) {
|
|
|
|
// repoisition the busy notification (centered)
|
|
|
|
busyNotification->layout(useableWidth(), getVisibleRect().topLeft()
|
|
|
|
+ QPointF(0, getVisibleRect().height() / 2.0));
|
2015-01-25 21:06:06 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-15 18:48:41 +08:00
|
|
|
ChatLine::Ptr ChatLog::findLineByPosY(qreal yPos) const
|
2015-01-15 02:43:52 +08:00
|
|
|
{
|
2015-01-15 18:48:41 +08:00
|
|
|
auto itr = std::lower_bound(lines.cbegin(), lines.cend(), yPos, ChatLine::lessThanBSRectBottom);
|
2015-01-15 02:43:52 +08:00
|
|
|
|
2015-03-21 02:38:10 +08:00
|
|
|
if (itr != lines.cend())
|
2015-01-15 18:48:41 +08:00
|
|
|
return *itr;
|
2015-01-15 02:43:52 +08:00
|
|
|
|
|
|
|
return ChatLine::Ptr();
|
|
|
|
}
|
|
|
|
|
2015-01-14 18:27:52 +08:00
|
|
|
QRectF ChatLog::calculateSceneRect() const
|
|
|
|
{
|
2015-02-01 00:49:19 +08:00
|
|
|
qreal bottom = (lines.empty() ? 0.0 : lines.last()->sceneBoundingRect().bottom());
|
2015-01-14 18:27:52 +08:00
|
|
|
|
2020-03-01 10:12:40 +08:00
|
|
|
if (typingNotification.get() != nullptr) {
|
2015-02-01 00:49:19 +08:00
|
|
|
bottom += typingNotification->sceneBoundingRect().height() + lineSpacing;
|
2020-03-01 10:12:40 +08:00
|
|
|
}
|
2015-01-14 18:27:52 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
return QRectF(-margins.left(), -margins.top(), useableWidth(),
|
|
|
|
bottom + margins.bottom() + margins.top());
|
2015-01-14 18:27:52 +08:00
|
|
|
}
|
|
|
|
|
2015-01-04 22:18:23 +08:00
|
|
|
void ChatLog::onSelectionTimerTimeout()
|
|
|
|
{
|
|
|
|
const int scrollSpeed = 10;
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
switch (selectionScrollDir) {
|
2019-09-29 08:07:06 +08:00
|
|
|
case AutoScrollDirection::Up:
|
2015-01-04 22:18:23 +08:00
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->value() - scrollSpeed);
|
|
|
|
break;
|
2019-09-29 08:07:06 +08:00
|
|
|
case AutoScrollDirection::Down:
|
2015-01-04 22:18:23 +08:00
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->value() + scrollSpeed);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-01-25 21:06:06 +08:00
|
|
|
|
|
|
|
void ChatLog::onWorkerTimeout()
|
|
|
|
{
|
|
|
|
// Fairly arbitrary but
|
|
|
|
// large values will make the UI unresponsive
|
2015-02-01 01:37:20 +08:00
|
|
|
const int stepSize = 50;
|
2015-01-25 21:06:06 +08:00
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
layout(workerLastIndex, workerLastIndex + stepSize, useableWidth());
|
2015-01-25 21:06:06 +08:00
|
|
|
workerLastIndex += stepSize;
|
|
|
|
|
|
|
|
// done?
|
2017-02-26 19:52:45 +08:00
|
|
|
if (workerLastIndex >= lines.size()) {
|
2015-01-25 21:06:06 +08:00
|
|
|
workerTimer->stop();
|
|
|
|
|
|
|
|
// switch back to the scene containing the chat messages
|
|
|
|
setScene(scene);
|
|
|
|
|
|
|
|
// make sure everything gets updated
|
|
|
|
updateSceneRect();
|
|
|
|
checkVisibility();
|
|
|
|
updateTypingNotification();
|
|
|
|
updateMultiSelectionRect();
|
|
|
|
|
|
|
|
// scroll
|
2019-08-23 21:48:21 +08:00
|
|
|
if (workerStb) {
|
2015-01-25 21:06:06 +08:00
|
|
|
scrollToBottom();
|
2019-08-23 21:48:21 +08:00
|
|
|
workerStb = false;
|
|
|
|
} else {
|
2015-01-25 21:06:06 +08:00
|
|
|
scrollToLine(workerAnchorLine);
|
2019-08-23 21:48:21 +08:00
|
|
|
}
|
2015-01-25 21:06:06 +08:00
|
|
|
|
|
|
|
// don't keep a Ptr to the anchor line
|
|
|
|
workerAnchorLine = ChatLine::Ptr();
|
|
|
|
|
|
|
|
// hidden during busy screen
|
|
|
|
verticalScrollBar()->show();
|
2018-02-10 23:50:48 +08:00
|
|
|
|
2019-07-01 19:27:58 +08:00
|
|
|
isScroll = true;
|
2019-08-12 08:39:57 +08:00
|
|
|
emit workerTimeoutFinished();
|
2015-01-25 21:06:06 +08:00
|
|
|
}
|
|
|
|
}
|
2015-01-27 16:58:08 +08:00
|
|
|
|
2017-03-28 23:20:01 +08:00
|
|
|
void ChatLog::onMultiClickTimeout()
|
|
|
|
{
|
|
|
|
clickCount = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::handleMultiClickEvent()
|
|
|
|
{
|
|
|
|
// Ignore single or double clicks
|
|
|
|
if (clickCount < 2)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (clickCount) {
|
2017-06-01 15:50:59 +08:00
|
|
|
case 3:
|
|
|
|
QPointF scenePos = mapToScene(lastClickPos);
|
|
|
|
ChatLineContent* content = getContentFromPos(scenePos);
|
2017-03-28 23:20:01 +08:00
|
|
|
|
2017-06-01 15:50:59 +08:00
|
|
|
if (content) {
|
|
|
|
content->selectionTripleClick(scenePos);
|
|
|
|
selClickedCol = content->getColumn();
|
|
|
|
selClickedRow = content->getRow();
|
|
|
|
selFirstRow = content->getRow();
|
|
|
|
selLastRow = content->getRow();
|
2019-09-29 08:07:06 +08:00
|
|
|
selectionMode = SelectionMode::Precise;
|
2017-03-28 23:20:01 +08:00
|
|
|
|
2017-06-01 15:50:59 +08:00
|
|
|
emit selectionChanged();
|
|
|
|
}
|
|
|
|
break;
|
2017-03-28 23:20:01 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-03 01:11:29 +08:00
|
|
|
void ChatLog::showEvent(QShowEvent*)
|
2015-01-27 16:58:08 +08:00
|
|
|
{
|
|
|
|
// Empty.
|
|
|
|
// The default implementation calls centerOn - for some reason - causing
|
|
|
|
// the scrollbar to move.
|
|
|
|
}
|
2015-02-03 01:11:29 +08:00
|
|
|
|
|
|
|
void ChatLog::focusInEvent(QFocusEvent* ev)
|
|
|
|
{
|
|
|
|
QGraphicsView::focusInEvent(ev);
|
2015-02-03 17:33:46 +08:00
|
|
|
|
2019-09-29 08:07:06 +08:00
|
|
|
if (selectionMode != SelectionMode::None) {
|
2015-02-03 17:33:46 +08:00
|
|
|
selGraphItem->setBrush(QBrush(selectionRectColor));
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
for (int i = selFirstRow; i <= selLastRow; ++i)
|
2015-02-03 17:33:46 +08:00
|
|
|
lines[i]->selectionFocusChanged(true);
|
|
|
|
}
|
2015-02-03 01:11:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::focusOutEvent(QFocusEvent* ev)
|
|
|
|
{
|
|
|
|
QGraphicsView::focusOutEvent(ev);
|
2015-02-03 17:33:46 +08:00
|
|
|
|
2019-09-29 08:07:06 +08:00
|
|
|
if (selectionMode != SelectionMode::None) {
|
2015-02-03 17:33:46 +08:00
|
|
|
selGraphItem->setBrush(QBrush(selectionRectColor.lighter(120)));
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
for (int i = selFirstRow; i <= selLastRow; ++i)
|
2015-02-03 17:33:46 +08:00
|
|
|
lines[i]->selectionFocusChanged(false);
|
|
|
|
}
|
2015-02-03 01:11:29 +08:00
|
|
|
}
|
2015-06-06 03:37:01 +08:00
|
|
|
|
2019-05-27 04:22:15 +08:00
|
|
|
void ChatLog::wheelEvent(QWheelEvent *event)
|
|
|
|
{
|
2019-07-01 19:27:58 +08:00
|
|
|
if (!isScroll) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-05-27 04:22:15 +08:00
|
|
|
QGraphicsView::wheelEvent(event);
|
|
|
|
checkVisibility(true);
|
|
|
|
}
|
|
|
|
|
2015-06-06 03:37:01 +08:00
|
|
|
void ChatLog::retranslateUi()
|
|
|
|
{
|
|
|
|
copyAction->setText(tr("Copy"));
|
|
|
|
selectAllAction->setText(tr("Select all"));
|
|
|
|
}
|
2016-07-30 21:15:52 +08:00
|
|
|
|
|
|
|
bool ChatLog::isActiveFileTransfer(ChatLine::Ptr l)
|
|
|
|
{
|
|
|
|
int count = l->getColumnCount();
|
2017-02-26 19:52:45 +08:00
|
|
|
for (int i = 0; i < count; ++i) {
|
|
|
|
ChatLineContent* content = l->getContent(i);
|
|
|
|
ChatLineContentProxy* proxy = qobject_cast<ChatLineContentProxy*>(content);
|
2016-07-30 21:15:52 +08:00
|
|
|
if (!proxy)
|
|
|
|
continue;
|
|
|
|
|
2017-01-06 19:02:54 +08:00
|
|
|
QWidget* widget = proxy->getWidget();
|
2017-02-26 19:52:45 +08:00
|
|
|
FileTransferWidget* transferWidget = qobject_cast<FileTransferWidget*>(widget);
|
2016-07-30 21:15:52 +08:00
|
|
|
if (transferWidget && transferWidget->isActive())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2019-09-29 08:01:48 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Adjusts the selection based on chatlog changing lines
|
|
|
|
* @param offset Amount to shift selection rect up by. Must be non-negative.
|
|
|
|
*/
|
|
|
|
void ChatLog::moveSelectionRectUpIfSelected(int offset)
|
|
|
|
{
|
|
|
|
assert(offset >= 0);
|
|
|
|
switch (selectionMode)
|
|
|
|
{
|
|
|
|
case SelectionMode::None:
|
|
|
|
return;
|
|
|
|
case SelectionMode::Precise:
|
|
|
|
movePreciseSelectionUp(offset);
|
|
|
|
break;
|
|
|
|
case SelectionMode::Multi:
|
|
|
|
moveMultiSelectionUp(offset);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Adjusts the selections based on chatlog changing lines
|
|
|
|
* @param offset removed from the lines indexes. Must be non-negative.
|
|
|
|
*/
|
|
|
|
void ChatLog::moveSelectionRectDownIfSelected(int offset)
|
|
|
|
{
|
|
|
|
assert(offset >= 0);
|
|
|
|
switch (selectionMode)
|
|
|
|
{
|
|
|
|
case SelectionMode::None:
|
|
|
|
return;
|
|
|
|
case SelectionMode::Precise:
|
|
|
|
movePreciseSelectionDown(offset);
|
|
|
|
break;
|
|
|
|
case SelectionMode::Multi:
|
|
|
|
moveMultiSelectionDown(offset);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::movePreciseSelectionDown(int offset)
|
|
|
|
{
|
|
|
|
assert(selFirstRow == selLastRow && selFirstRow == selClickedRow);
|
|
|
|
const int lastLine = lines.size() - 1;
|
|
|
|
if (selClickedRow + offset > lastLine) {
|
|
|
|
clearSelection();
|
|
|
|
} else {
|
|
|
|
const int newRow = selClickedRow + offset;
|
|
|
|
selClickedRow = newRow;
|
|
|
|
selLastRow = newRow;
|
|
|
|
selFirstRow = newRow;
|
|
|
|
emit selectionChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::movePreciseSelectionUp(int offset)
|
|
|
|
{
|
|
|
|
assert(selFirstRow == selLastRow && selFirstRow == selClickedRow);
|
|
|
|
if (selClickedRow < offset) {
|
|
|
|
clearSelection();
|
|
|
|
} else {
|
|
|
|
const int newRow = selClickedRow - offset;
|
|
|
|
selClickedRow = newRow;
|
|
|
|
selLastRow = newRow;
|
|
|
|
selFirstRow = newRow;
|
|
|
|
emit selectionChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::moveMultiSelectionUp(int offset)
|
|
|
|
{
|
|
|
|
if (selLastRow < offset) { // entire selection now out of bounds
|
|
|
|
clearSelection();
|
|
|
|
} else {
|
|
|
|
selLastRow -= offset;
|
|
|
|
selClickedRow = std::max(0, selClickedRow - offset);
|
|
|
|
selFirstRow = std::max(0, selFirstRow - offset);
|
|
|
|
updateMultiSelectionRect();
|
|
|
|
emit selectionChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::moveMultiSelectionDown(int offset)
|
|
|
|
{
|
|
|
|
const int lastLine = lines.size() - 1;
|
|
|
|
if (selFirstRow + offset > lastLine) { // entire selection now out of bounds
|
|
|
|
clearSelection();
|
|
|
|
} else {
|
|
|
|
selFirstRow += offset;
|
|
|
|
selClickedRow = std::min(lastLine, selClickedRow + offset);
|
|
|
|
selLastRow = std::min(lastLine, selLastRow + offset);
|
|
|
|
updateMultiSelectionRect();
|
|
|
|
emit selectionChanged();
|
|
|
|
}
|
|
|
|
}
|