2014-11-16 19:58:43 +08:00
|
|
|
/*
|
|
|
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
|
|
|
|
|
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
|
|
|
|
|
|
This program is libre software: you can redistribute it and/or modify
|
|
|
|
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.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
|
|
|
|
See the COPYING file for more details.
|
|
|
|
*/
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
#include "chatlog.h"
|
2014-11-12 23:45:24 +08:00
|
|
|
#include "chatmessage.h"
|
2014-11-12 21:11:25 +08:00
|
|
|
#include "chatlinecontent.h"
|
2014-11-16 19:40:44 +08:00
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
#include <QDebug>
|
|
|
|
#include <QScrollBar>
|
|
|
|
#include <QApplication>
|
|
|
|
#include <QClipboard>
|
2015-01-04 21:21:05 +08:00
|
|
|
#include <QAction>
|
2015-01-04 22:18:23 +08:00
|
|
|
#include <QTimer>
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
template<class T>
|
|
|
|
T clamp(T x, T min, T max)
|
|
|
|
{
|
|
|
|
if(x > max)
|
|
|
|
return max;
|
|
|
|
if(x < min)
|
|
|
|
return min;
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
|
|
|
ChatLog::ChatLog(QWidget* parent)
|
|
|
|
: QGraphicsView(parent)
|
|
|
|
{
|
|
|
|
scene = new QGraphicsScene(this);
|
|
|
|
scene->setItemIndexMethod(QGraphicsScene::NoIndex);
|
|
|
|
setScene(scene);
|
|
|
|
|
|
|
|
setInteractive(true);
|
|
|
|
setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
|
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
setDragMode(QGraphicsView::NoDrag);
|
2015-01-04 04:19:52 +08:00
|
|
|
setViewportUpdateMode(BoundingRectViewportUpdate);
|
2014-11-12 21:11:25 +08:00
|
|
|
//setRenderHint(QPainter::TextAntialiasing);
|
2015-01-02 20:04:40 +08:00
|
|
|
setAcceptDrops(false);
|
2015-01-04 21:21:05 +08:00
|
|
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2014-12-10 02:24:01 +08:00
|
|
|
const QColor selGraphColor = QColor(166,225,255);
|
|
|
|
selGraphItem = scene->addRect(0,0,0,0,selGraphColor.darker(120),selGraphColor);
|
2014-12-09 20:17:08 +08:00
|
|
|
selGraphItem->setZValue(-10.0); //behind all items
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
// copy action
|
|
|
|
copyAction = new QAction(this);
|
|
|
|
copyAction->setShortcut(QKeySequence::Copy);
|
|
|
|
addAction(copyAction);
|
|
|
|
connect(copyAction, &QAction::triggered, this, [ = ](bool)
|
|
|
|
{
|
|
|
|
copySelectedText();
|
|
|
|
});
|
2015-01-04 22:18:23 +08:00
|
|
|
|
|
|
|
selectionTimer = new QTimer(this);
|
|
|
|
selectionTimer->setInterval(1000/60);
|
|
|
|
selectionTimer->setSingleShot(false);
|
|
|
|
selectionTimer->start();
|
|
|
|
connect(selectionTimer, &QTimer::timeout, this, &ChatLog::onSelectionTimerTimeout);
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
ChatLog::~ChatLog()
|
|
|
|
{
|
2015-01-04 20:29:14 +08:00
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr ChatLog::addChatMessage(const QString& sender, const QString &msg, bool self, bool alert)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr line = ChatMessage::createChatMessage(scene, sender, msg, false, alert, self);
|
|
|
|
insertChatline(std::dynamic_pointer_cast<ChatLine>(line));
|
2015-01-03 22:03:33 +08:00
|
|
|
|
2014-11-12 23:45:24 +08:00
|
|
|
return line;
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr ChatLog::addChatMessage(const QString& sender, const QString& msg, const QDateTime& timestamp, bool self, bool alert)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr line = ChatMessage::createChatMessage(scene, sender, msg, false, alert, self, timestamp);
|
|
|
|
insertChatline(std::dynamic_pointer_cast<ChatLine>(line));
|
2014-11-12 23:45:24 +08:00
|
|
|
|
|
|
|
return line;
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr ChatLog::addChatAction(const QString &sender, const QString &msg, const QDateTime ×tamp)
|
2014-12-14 04:11:03 +08:00
|
|
|
{
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr line = ChatMessage::createChatMessage(scene, sender, msg, true, false, false, timestamp);
|
|
|
|
insertChatline(std::dynamic_pointer_cast<ChatLine>(line));
|
2014-12-14 04:11:03 +08:00
|
|
|
|
|
|
|
return line;
|
|
|
|
}
|
|
|
|
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr ChatLog::addChatAction(const QString &sender, const QString &msg)
|
2014-12-14 04:11:03 +08:00
|
|
|
{
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr line = ChatMessage::createChatMessage(scene, sender, msg, true, false, false);
|
|
|
|
insertChatline(std::dynamic_pointer_cast<ChatLine>(line));
|
2015-01-03 22:03:33 +08:00
|
|
|
|
2014-12-14 04:11:03 +08:00
|
|
|
return line;
|
|
|
|
}
|
|
|
|
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr ChatLog::addSystemMessage(const QString &msg, const QDateTime& timestamp)
|
2014-11-16 19:40:44 +08:00
|
|
|
{
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr line = ChatMessage::createChatInfoMessage(scene, msg, "", timestamp);
|
|
|
|
insertChatline(std::dynamic_pointer_cast<ChatLine>(line));
|
2015-01-03 22:03:33 +08:00
|
|
|
|
2014-11-16 19:40:44 +08:00
|
|
|
return line;
|
|
|
|
}
|
|
|
|
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr ChatLog::addFileTransferMessage(const QString &sender, const ToxFile &file, const QDateTime& timestamp, bool self)
|
2014-11-17 03:01:37 +08:00
|
|
|
{
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatMessage::Ptr line = ChatMessage::createFileTransferMessage(scene, sender, "", file, self, timestamp);
|
|
|
|
insertChatline(std::dynamic_pointer_cast<ChatLine>(line));
|
2015-01-03 22:03:33 +08:00
|
|
|
|
2014-11-17 03:01:37 +08:00
|
|
|
return line;
|
|
|
|
}
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
void ChatLog::clearSelection()
|
|
|
|
{
|
2014-12-09 20:17:08 +08:00
|
|
|
for(int i=selFirstRow; i<=selLastRow && i<lines.size() && i >= 0; ++i)
|
|
|
|
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;
|
|
|
|
|
|
|
|
selectionMode = None;
|
2014-12-10 04:47:25 +08:00
|
|
|
|
|
|
|
updateMultiSelectionRect();
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
QRect ChatLog::getVisibleRect() const
|
|
|
|
{
|
|
|
|
return mapToScene(viewport()->rect()).boundingRect().toRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::updateSceneRect()
|
|
|
|
{
|
2014-12-10 04:47:25 +08:00
|
|
|
setSceneRect(QRectF(-margins.left(), -margins.top(), useableWidth(), (lines.empty() ? 0.0 : lines.last()->boundingSceneRect().bottom()) + margins.bottom() + margins.top()));
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool ChatLog::layout(int start, int end, qreal width)
|
|
|
|
{
|
|
|
|
//qDebug() << "layout " << start << end;
|
|
|
|
if(lines.empty())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
start = clamp<int>(start, 0, lines.size() - 1);
|
|
|
|
end = clamp<int>(end + 1, 0, lines.size());
|
|
|
|
|
|
|
|
qreal h = lines[start]->boundingSceneRect().top();
|
|
|
|
|
|
|
|
bool needsReposition = false;
|
|
|
|
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
|
|
|
|
|
|
|
qreal oldHeight = l->boundingSceneRect().height();
|
2014-12-10 04:47:25 +08:00
|
|
|
l->layout(width, QPointF(0.0, h));
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
if(oldHeight != l->boundingSceneRect().height())
|
|
|
|
needsReposition = true;
|
|
|
|
|
|
|
|
h += l->boundingSceneRect().height() + lineSpacing;
|
|
|
|
}
|
|
|
|
|
|
|
|
// move up
|
|
|
|
if(needsReposition)
|
|
|
|
reposition(end-1, end+10);
|
|
|
|
|
|
|
|
return needsReposition;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::partialUpdate()
|
|
|
|
{
|
|
|
|
checkVisibility();
|
|
|
|
|
|
|
|
if(visibleLines.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto oldUpdateMode = viewportUpdateMode();
|
|
|
|
setViewportUpdateMode(NoViewportUpdate);
|
|
|
|
|
|
|
|
bool repos;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
repos = false;
|
|
|
|
if(!visibleLines.empty())
|
|
|
|
repos = layout(visibleLines.first()->getRowIndex(), visibleLines.last()->getRowIndex(), useableWidth());
|
|
|
|
|
|
|
|
checkVisibility();
|
|
|
|
}
|
|
|
|
while(repos);
|
|
|
|
|
2015-01-03 22:32:12 +08:00
|
|
|
if(!visibleLines.empty())
|
|
|
|
reposition(visibleLines.last()->getRowIndex(), lines.size());
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
checkVisibility();
|
|
|
|
|
|
|
|
setViewportUpdateMode(oldUpdateMode);
|
|
|
|
updateSceneRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::fullUpdate()
|
|
|
|
{
|
|
|
|
layout(0, lines.size(), useableWidth());
|
|
|
|
checkVisibility();
|
|
|
|
updateSceneRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::mousePressEvent(QMouseEvent* ev)
|
|
|
|
{
|
|
|
|
QGraphicsView::mousePressEvent(ev);
|
|
|
|
|
|
|
|
QPointF scenePos = mapToScene(ev->pos());
|
|
|
|
|
|
|
|
if(ev->button() == Qt::LeftButton)
|
|
|
|
{
|
|
|
|
clickPos = ev->pos();
|
|
|
|
clearSelection();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(ev->button() == Qt::RightButton)
|
|
|
|
{
|
|
|
|
if(!isOverSelection(scenePos))
|
|
|
|
clearSelection();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::mouseReleaseEvent(QMouseEvent* ev)
|
|
|
|
{
|
|
|
|
QGraphicsView::mouseReleaseEvent(ev);
|
2015-01-04 21:21:05 +08:00
|
|
|
|
|
|
|
QPointF scenePos = mapToScene(ev->pos());
|
|
|
|
|
|
|
|
if(ev->button() == Qt::RightButton)
|
|
|
|
{
|
|
|
|
if(!isOverSelection(scenePos))
|
|
|
|
clearSelection();
|
|
|
|
|
|
|
|
emit customContextMenuRequested(ev->pos());
|
|
|
|
}
|
2015-01-04 22:18:23 +08:00
|
|
|
|
|
|
|
selectionScrollDir = NoDirection;
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::mouseMoveEvent(QMouseEvent* ev)
|
|
|
|
{
|
|
|
|
QGraphicsView::mouseMoveEvent(ev);
|
|
|
|
|
|
|
|
QPointF scenePos = mapToScene(ev->pos());
|
|
|
|
|
|
|
|
if(ev->buttons() & Qt::LeftButton)
|
|
|
|
{
|
2015-01-04 22:18:23 +08:00
|
|
|
//autoscroll
|
|
|
|
if(ev->pos().y() < 0)
|
|
|
|
selectionScrollDir = Up;
|
|
|
|
else if(ev->pos().y() > height())
|
|
|
|
selectionScrollDir = Down;
|
|
|
|
else
|
|
|
|
selectionScrollDir = NoDirection;
|
|
|
|
|
|
|
|
//select
|
2014-12-09 20:17:08 +08:00
|
|
|
if(selectionMode == None && (clickPos - ev->pos()).manhattanLength() > QApplication::startDragDistance())
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
|
|
|
QPointF sceneClickPos = mapToScene(clickPos.toPoint());
|
|
|
|
|
|
|
|
ChatLineContent* content = getContentFromPos(sceneClickPos);
|
|
|
|
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);
|
|
|
|
|
2014-12-09 20:17:08 +08:00
|
|
|
selectionMode = Precise;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
// ungrab mouse grabber
|
|
|
|
if(scene->mouseGrabberItem())
|
|
|
|
scene->mouseGrabberItem()->ungrabMouse();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
if(selectionMode != None && ev->pos() != lastPos)
|
|
|
|
{
|
|
|
|
lastPos = ev->pos();
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
ChatLineContent* content = getContentFromPos(scenePos);
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
if(content)
|
|
|
|
{
|
|
|
|
int row = content->getRow();
|
|
|
|
int col = content->getColumn();
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
if(row >= selClickedRow)
|
|
|
|
selLastRow = row;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
if(row <= selClickedRow)
|
|
|
|
selFirstRow = row;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
if(row == selClickedRow && col == selClickedCol)
|
|
|
|
{
|
|
|
|
selectionMode = Precise;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
content->selectionMouseMove(scenePos);
|
|
|
|
selGraphItem->hide();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
selectionMode = Multi;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
lines[selClickedRow]->selectionCleared();
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2015-01-02 18:25:07 +08:00
|
|
|
updateMultiSelectionRect();
|
|
|
|
}
|
2014-12-09 20:17:08 +08:00
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ChatLineContent* ChatLog::getContentFromPos(QPointF scenePos) const
|
|
|
|
{
|
|
|
|
QGraphicsItem* item = scene->itemAt(scenePos, QTransform());
|
|
|
|
|
|
|
|
if(item && item->type() == ChatLineContent::ChatLineContentType)
|
|
|
|
return static_cast<ChatLineContent*>(item);
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ChatLog::isOverSelection(QPointF scenePos)
|
|
|
|
{
|
2014-12-09 20:17:08 +08:00
|
|
|
if(selectionMode == Precise)
|
|
|
|
{
|
|
|
|
ChatLineContent* content = getContentFromPos(scenePos);
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2014-12-09 20:17:08 +08:00
|
|
|
if(content)
|
|
|
|
return content->isOverSelection(scenePos);
|
|
|
|
}
|
|
|
|
else if(selectionMode == Multi)
|
|
|
|
{
|
|
|
|
if(selGraphItem->rect().contains(scenePos))
|
|
|
|
return true;
|
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-12-10 04:47:25 +08:00
|
|
|
qreal ChatLog::useableWidth()
|
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
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::reposition(int start, int end)
|
|
|
|
{
|
|
|
|
if(lines.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
start = clamp<int>(start, 0, lines.size() - 1);
|
|
|
|
end = clamp<int>(end + 1, 0, lines.size());
|
|
|
|
|
|
|
|
qreal h = lines[start]->boundingSceneRect().bottom() + lineSpacing;
|
|
|
|
|
|
|
|
for(int i = start + 1; i < end; ++i)
|
|
|
|
{
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatLine* l = lines[i].get();
|
2014-11-12 21:11:25 +08:00
|
|
|
l->layout(QPointF(0, h));
|
|
|
|
h += l->boundingSceneRect().height() + lineSpacing;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::repositionDownTo(int start, qreal end)
|
|
|
|
{
|
|
|
|
if(lines.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
start = clamp<int>(start, 0, lines.size() - 1);
|
|
|
|
|
|
|
|
qreal h = lines[start]->boundingSceneRect().bottom() + lineSpacing;
|
|
|
|
|
|
|
|
for(int i = start + 1; i < lines.size(); ++i)
|
|
|
|
{
|
2015-01-04 20:29:14 +08:00
|
|
|
ChatLine* l = lines[i].get();
|
2014-11-12 21:11:25 +08:00
|
|
|
l->layout(QPointF(0, h));
|
|
|
|
h += l->boundingSceneRect().height() + lineSpacing;
|
|
|
|
|
|
|
|
if(h > end)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-04 20:29:14 +08:00
|
|
|
void ChatLog::insertChatline(ChatLine::Ptr l)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
|
|
|
stickToBtm = stickToBottom();
|
|
|
|
|
|
|
|
l->setRowIndex(lines.size());
|
|
|
|
lines.append(l);
|
|
|
|
|
|
|
|
layout(lines.last()->getRowIndex() - 1, lines.size(), useableWidth());
|
|
|
|
updateSceneRect();
|
|
|
|
|
|
|
|
if(stickToBtm)
|
|
|
|
scrollToBottom();
|
|
|
|
|
|
|
|
checkVisibility();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ChatLog::stickToBottom()
|
|
|
|
{
|
|
|
|
return verticalScrollBar()->value() == verticalScrollBar()->maximum();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::scrollToBottom()
|
|
|
|
{
|
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
|
|
|
updateGeometry();
|
|
|
|
checkVisibility();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString ChatLog::getSelectedText() const
|
|
|
|
{
|
2014-12-09 20:17:08 +08:00
|
|
|
if(selectionMode == Precise)
|
|
|
|
{
|
|
|
|
return lines[selClickedRow]->content[selClickedCol]->getSelectedText();
|
|
|
|
}
|
|
|
|
else if(selectionMode == Multi)
|
|
|
|
{
|
|
|
|
// build a nicely formatted message
|
|
|
|
QString out;
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2014-12-09 20:17:08 +08:00
|
|
|
QString lastSender;
|
|
|
|
for(int i=selFirstRow; i<=selLastRow && i>=0 && i<lines.size(); ++i)
|
|
|
|
{
|
|
|
|
if(lastSender != lines[i]->content[0]->getText() && !lines[i]->content[0]->getText().isEmpty())
|
|
|
|
{
|
|
|
|
//author changed
|
|
|
|
out += lines[i]->content[0]->getText() + ":\n";
|
|
|
|
lastSender = lines[i]->content[0]->getText();
|
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
|
2014-12-09 20:17:08 +08:00
|
|
|
out += lines[i]->content[1]->getText();
|
|
|
|
out += "\n\n";
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2015-01-02 19:04:04 +08:00
|
|
|
QString ChatLog::toPlainText() const
|
|
|
|
{
|
|
|
|
QString out;
|
|
|
|
QString lastSender;
|
|
|
|
|
2015-01-04 20:29:14 +08:00
|
|
|
for(ChatLine::Ptr l : lines)
|
2015-01-02 19:04:04 +08:00
|
|
|
{
|
|
|
|
if(lastSender != l->content[0]->getText() && !l->content[0]->getText().isEmpty())
|
|
|
|
{
|
|
|
|
//author changed
|
|
|
|
out += l->content[0]->getText() + ":\n";
|
|
|
|
lastSender = l->content[0]->getText();
|
|
|
|
}
|
|
|
|
|
|
|
|
out += l->content[1]->getText();
|
|
|
|
out += "\n\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
2015-01-04 21:21:05 +08:00
|
|
|
return selectionMode != None;
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::clear()
|
|
|
|
{
|
|
|
|
clearSelection();
|
|
|
|
|
2015-01-04 20:29:14 +08:00
|
|
|
for(ChatLine::Ptr l : lines)
|
|
|
|
l->removeFromScene();
|
2014-11-12 21:11:25 +08:00
|
|
|
|
|
|
|
lines.clear();
|
2015-01-04 20:29:14 +08:00
|
|
|
visibleLines.clear();
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
updateSceneRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::copySelectedText() const
|
|
|
|
{
|
|
|
|
QString text = getSelectedText();
|
|
|
|
|
|
|
|
QClipboard* clipboard = QApplication::clipboard();
|
|
|
|
clipboard->setText(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::checkVisibility()
|
|
|
|
{
|
2015-01-04 04:19:52 +08:00
|
|
|
if(lines.empty())
|
|
|
|
return;
|
|
|
|
|
2014-11-12 21:11:25 +08:00
|
|
|
// find first visible row
|
2015-01-04 20:29:14 +08:00
|
|
|
QList<ChatLine::Ptr>::const_iterator upperBound;
|
|
|
|
upperBound = std::upper_bound(lines.cbegin(), lines.cend(), getVisibleRect().top(), [](const qreal lhs, const ChatLine::Ptr rhs)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
|
|
|
return lhs < rhs->boundingSceneRect().bottom();
|
|
|
|
});
|
|
|
|
|
|
|
|
// find last visible row
|
2015-01-04 20:29:14 +08:00
|
|
|
QList<ChatLine::Ptr>::const_iterator lowerBound;
|
|
|
|
lowerBound = std::lower_bound(lines.cbegin(), lines.cend(), getVisibleRect().bottom(), [](const ChatLine::Ptr lhs, const qreal rhs)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
2015-01-04 04:19:52 +08:00
|
|
|
return lhs->boundingSceneRect().top() < rhs;
|
2014-11-12 21:11:25 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
// set visibilty
|
2015-01-04 20:29:14 +08:00
|
|
|
QList<ChatLine::Ptr> newVisibleLines;
|
2015-01-04 04:19:52 +08:00
|
|
|
for(auto itr = upperBound; itr != lowerBound; ++itr)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
|
|
|
newVisibleLines.append(*itr);
|
|
|
|
|
|
|
|
if(!visibleLines.contains(*itr))
|
|
|
|
(*itr)->visibilityChanged(true);
|
|
|
|
|
|
|
|
visibleLines.removeOne(*itr);
|
|
|
|
}
|
|
|
|
|
2015-01-04 20:29:14 +08:00
|
|
|
for(ChatLine::Ptr line : visibleLines)
|
2014-11-12 21:11:25 +08:00
|
|
|
line->visibilityChanged(false);
|
|
|
|
|
|
|
|
visibleLines = newVisibleLines;
|
|
|
|
|
|
|
|
// assure order
|
2015-01-04 20:29:14 +08:00
|
|
|
std::sort(visibleLines.begin(), visibleLines.end(), [](const ChatLine::Ptr lhs, const ChatLine::Ptr rhs)
|
2014-11-12 21:11:25 +08:00
|
|
|
{
|
|
|
|
return lhs->getRowIndex() < rhs->getRowIndex();
|
|
|
|
});
|
|
|
|
|
|
|
|
//if(!visibleLines.empty())
|
|
|
|
// qDebug() << "visible from " << visibleLines.first()->getRowIndex() << "to " << visibleLines.last()->getRowIndex() << " total " << visibleLines.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::scrollContentsBy(int dx, int dy)
|
|
|
|
{
|
|
|
|
QGraphicsView::scrollContentsBy(dx, dy);
|
|
|
|
partialUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::resizeEvent(QResizeEvent* ev)
|
|
|
|
{
|
|
|
|
bool stb = stickToBottom();
|
|
|
|
|
|
|
|
QGraphicsView::resizeEvent(ev);
|
|
|
|
|
|
|
|
if(lines.count() > 300)
|
|
|
|
partialUpdate();
|
|
|
|
else
|
|
|
|
fullUpdate();
|
|
|
|
|
|
|
|
if(stb)
|
|
|
|
scrollToBottom();
|
2014-12-10 04:47:25 +08:00
|
|
|
|
|
|
|
updateMultiSelectionRect();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatLog::updateMultiSelectionRect()
|
|
|
|
{
|
|
|
|
if(selectionMode == Multi && selFirstRow >= 0 && selLastRow >= 0)
|
|
|
|
{
|
|
|
|
QRectF selBBox;
|
|
|
|
selBBox = selBBox.united(lines[selFirstRow]->boundingSceneRect());
|
|
|
|
selBBox = selBBox.united(lines[selLastRow]->boundingSceneRect());
|
|
|
|
|
|
|
|
selGraphItem->setRect(selBBox);
|
|
|
|
selGraphItem->show();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
selGraphItem->hide();
|
|
|
|
}
|
2014-11-12 21:11:25 +08:00
|
|
|
}
|
2015-01-04 22:18:23 +08:00
|
|
|
|
|
|
|
void ChatLog::onSelectionTimerTimeout()
|
|
|
|
{
|
|
|
|
const int scrollSpeed = 10;
|
|
|
|
|
|
|
|
switch(selectionScrollDir)
|
|
|
|
{
|
|
|
|
case Up:
|
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->value() - scrollSpeed);
|
|
|
|
break;
|
|
|
|
case Down:
|
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->value() + scrollSpeed);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|