mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
ChatLog: busy-screen during resize
Gets rid of most ugly hacks
This commit is contained in:
parent
9cb7ba06ed
commit
6e05782fb7
|
@ -40,6 +40,7 @@ ChatLog::ChatLog(QWidget* parent)
|
||||||
: QGraphicsView(parent)
|
: QGraphicsView(parent)
|
||||||
{
|
{
|
||||||
// Create the scene
|
// Create the scene
|
||||||
|
busyScene = new QGraphicsScene(this);
|
||||||
scene = new QGraphicsScene(this);
|
scene = new QGraphicsScene(this);
|
||||||
scene->setItemIndexMethod(QGraphicsScene::NoIndex); //Bsp-Tree is actually slower in this case
|
scene->setItemIndexMethod(QGraphicsScene::NoIndex); //Bsp-Tree is actually slower in this case
|
||||||
setScene(scene);
|
setScene(scene);
|
||||||
|
@ -80,43 +81,7 @@ ChatLog::ChatLog(QWidget* parent)
|
||||||
workerTimer = new QTimer(this);
|
workerTimer = new QTimer(this);
|
||||||
workerTimer->setSingleShot(false);
|
workerTimer->setSingleShot(false);
|
||||||
workerTimer->setInterval(100);
|
workerTimer->setInterval(100);
|
||||||
connect(workerTimer, &QTimer::timeout, this, [this] {
|
connect(workerTimer, &QTimer::timeout, this, &ChatLog::onWorkerTimeout);
|
||||||
const int stepSize = 400;
|
|
||||||
|
|
||||||
workerDy += layout(workerLastIndex, workerLastIndex+stepSize, useableWidth());
|
|
||||||
|
|
||||||
if(!visibleLines.isEmpty())
|
|
||||||
{
|
|
||||||
int firstVisLineIndex = visibleLines.first()->getRow();
|
|
||||||
int delta = firstVisLineIndex - workerLastIndex;
|
|
||||||
if(delta > 0 && delta < stepSize)
|
|
||||||
{
|
|
||||||
workerLastIndex += delta+1;
|
|
||||||
|
|
||||||
if(!workerStb)
|
|
||||||
verticalScrollBar()->setValue(verticalScrollBar()->value() - workerDy);
|
|
||||||
|
|
||||||
workerDy = 0.0;
|
|
||||||
checkVisibility();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
workerLastIndex += stepSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
workerLastIndex += stepSize;
|
|
||||||
|
|
||||||
if(workerLastIndex >= lines.size())
|
|
||||||
{
|
|
||||||
workerTimer->stop();
|
|
||||||
workerLastIndex = 0;
|
|
||||||
workerDy = 0.0;
|
|
||||||
|
|
||||||
if(stickToBottom())
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatLog::clearSelection()
|
void ChatLog::clearSelection()
|
||||||
|
@ -147,10 +112,10 @@ void ChatLog::updateSceneRect()
|
||||||
setSceneRect(calculateSceneRect());
|
setSceneRect(calculateSceneRect());
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal ChatLog::layout(int start, int end, qreal width)
|
void ChatLog::layout(int start, int end, qreal width)
|
||||||
{
|
{
|
||||||
if(lines.empty())
|
if(lines.empty())
|
||||||
return false;
|
return;
|
||||||
|
|
||||||
qreal h = 0.0;
|
qreal h = 0.0;
|
||||||
|
|
||||||
|
@ -162,59 +127,13 @@ qreal ChatLog::layout(int start, int end, qreal width)
|
||||||
start = clamp<int>(start, 0, lines.size());
|
start = clamp<int>(start, 0, lines.size());
|
||||||
end = clamp<int>(end + 1, 0, lines.size());
|
end = clamp<int>(end + 1, 0, lines.size());
|
||||||
|
|
||||||
qreal deltaY = 0.0;
|
|
||||||
for(int i = start; i < end; ++i)
|
for(int i = start; i < end; ++i)
|
||||||
{
|
{
|
||||||
ChatLine* l = lines[i].get();
|
ChatLine* l = lines[i].get();
|
||||||
|
|
||||||
qreal oldHeight = l->boundingSceneRect().height();
|
|
||||||
l->layout(width, QPointF(0.0, h));
|
l->layout(width, QPointF(0.0, h));
|
||||||
|
|
||||||
if(oldHeight != l->boundingSceneRect().height())
|
|
||||||
deltaY += oldHeight - l->boundingSceneRect().height();
|
|
||||||
|
|
||||||
h += l->boundingSceneRect().height() + lineSpacing;
|
h += l->boundingSceneRect().height() + lineSpacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
return deltaY;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatLog::updateVisibleLines()
|
|
||||||
{
|
|
||||||
checkVisibility();
|
|
||||||
|
|
||||||
if(visibleLines.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool stb = stickToBottom();
|
|
||||||
|
|
||||||
auto oldUpdateMode = viewportUpdateMode();
|
|
||||||
setViewportUpdateMode(NoViewportUpdate);
|
|
||||||
|
|
||||||
// Resize all lines currently visible in the viewport.
|
|
||||||
// If this creates some whitespace underneath the last visible lines
|
|
||||||
// then move the following non visible lines up in order to fill the gap.
|
|
||||||
qreal repos;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
repos = 0;
|
|
||||||
if(!visibleLines.empty())
|
|
||||||
{
|
|
||||||
repos = layout(visibleLines.first()->getRow(), visibleLines.last()->getRow(), useableWidth());
|
|
||||||
reposition(visibleLines.last()->getRow()+1, visibleLines.last()->getRow()+10, -repos);
|
|
||||||
verticalScrollBar()->setValue(verticalScrollBar()->value() - repos);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkVisibility();
|
|
||||||
}
|
|
||||||
while(repos != 0);
|
|
||||||
|
|
||||||
checkVisibility();
|
|
||||||
|
|
||||||
setViewportUpdateMode(oldUpdateMode);
|
|
||||||
|
|
||||||
if(stb)
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatLog::mousePressEvent(QMouseEvent* ev)
|
void ChatLog::mousePressEvent(QMouseEvent* ev)
|
||||||
|
@ -443,14 +362,12 @@ void ChatLog::insertChatlineOnTop(const QList<ChatLine::Ptr>& newLines)
|
||||||
if(newLines.isEmpty())
|
if(newLines.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool stickToBtm = stickToBottom();
|
// move all lines down by n
|
||||||
|
|
||||||
//move all lines down by n
|
|
||||||
int n = newLines.size();
|
int n = newLines.size();
|
||||||
for(ChatLine::Ptr l : lines)
|
for(ChatLine::Ptr l : lines)
|
||||||
l->setRow(l->getRow() + n);
|
l->setRow(l->getRow() + n);
|
||||||
|
|
||||||
//add the new line
|
// add the new line
|
||||||
for(ChatLine::Ptr l : newLines)
|
for(ChatLine::Ptr l : newLines)
|
||||||
{
|
{
|
||||||
l->addToScene(scene);
|
l->addToScene(scene);
|
||||||
|
@ -458,15 +375,8 @@ void ChatLog::insertChatlineOnTop(const QList<ChatLine::Ptr>& newLines)
|
||||||
lines.prepend(l);
|
lines.prepend(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
//full refresh is required
|
// redo layout
|
||||||
layout(0, lines.size(), useableWidth());
|
startResizeWorker();
|
||||||
updateSceneRect();
|
|
||||||
|
|
||||||
if(stickToBtm)
|
|
||||||
scrollToBottom();
|
|
||||||
|
|
||||||
checkVisibility();
|
|
||||||
updateTypingNotification();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatLog::stickToBottom() const
|
bool ChatLog::stickToBottom() const
|
||||||
|
@ -480,6 +390,27 @@ void ChatLog::scrollToBottom()
|
||||||
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatLog::startResizeWorker()
|
||||||
|
{
|
||||||
|
// (re)start the worker
|
||||||
|
if(!workerTimer->isActive())
|
||||||
|
{
|
||||||
|
// these values must not be reevaluated while the worker is running
|
||||||
|
workerStb = stickToBottom();
|
||||||
|
|
||||||
|
if(!visibleLines.empty())
|
||||||
|
workerAnchorLine = visibleLines.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
workerLastIndex = 0;
|
||||||
|
workerTimer->start();
|
||||||
|
|
||||||
|
// switch to busy scene displaying the busy notification
|
||||||
|
setScene(busyScene);
|
||||||
|
updateBusyNotification();
|
||||||
|
verticalScrollBar()->hide();
|
||||||
|
}
|
||||||
|
|
||||||
void ChatLog::mouseDoubleClickEvent(QMouseEvent *ev)
|
void ChatLog::mouseDoubleClickEvent(QMouseEvent *ev)
|
||||||
{
|
{
|
||||||
QPointF scenePos = mapToScene(ev->pos());
|
QPointF scenePos = mapToScene(ev->pos());
|
||||||
|
@ -580,6 +511,16 @@ void ChatLog::copySelectedText() const
|
||||||
clipboard->setText(text);
|
clipboard->setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatLog::setBusyNotification(ChatLine::Ptr notification)
|
||||||
|
{
|
||||||
|
if(!notification.get())
|
||||||
|
return;
|
||||||
|
|
||||||
|
busyNotification = notification;
|
||||||
|
busyNotification->addToScene(busyScene);
|
||||||
|
busyNotification->visibilityChanged(true);
|
||||||
|
}
|
||||||
|
|
||||||
void ChatLog::setTypingNotification(ChatLine::Ptr notification)
|
void ChatLog::setTypingNotification(ChatLine::Ptr notification)
|
||||||
{
|
{
|
||||||
typingNotification = notification;
|
typingNotification = notification;
|
||||||
|
@ -591,13 +532,22 @@ void ChatLog::setTypingNotification(ChatLine::Ptr notification)
|
||||||
|
|
||||||
void ChatLog::setTypingNotificationVisible(bool visible)
|
void ChatLog::setTypingNotificationVisible(bool visible)
|
||||||
{
|
{
|
||||||
if(typingNotification.get() != nullptr)
|
if(typingNotification.get())
|
||||||
{
|
{
|
||||||
typingNotification->setVisible(visible);
|
typingNotification->setVisible(visible);
|
||||||
updateTypingNotification();
|
updateTypingNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatLog::scrollToLine(ChatLine::Ptr line)
|
||||||
|
{
|
||||||
|
if(!line.get())
|
||||||
|
return;
|
||||||
|
|
||||||
|
updateSceneRect();
|
||||||
|
verticalScrollBar()->setValue(line->boundingSceneRect().top());
|
||||||
|
}
|
||||||
|
|
||||||
void ChatLog::checkVisibility()
|
void ChatLog::checkVisibility()
|
||||||
{
|
{
|
||||||
if(lines.empty())
|
if(lines.empty())
|
||||||
|
@ -637,28 +587,13 @@ void ChatLog::checkVisibility()
|
||||||
void ChatLog::scrollContentsBy(int dx, int dy)
|
void ChatLog::scrollContentsBy(int dx, int dy)
|
||||||
{
|
{
|
||||||
QGraphicsView::scrollContentsBy(dx, dy);
|
QGraphicsView::scrollContentsBy(dx, dy);
|
||||||
updateVisibleLines();
|
checkVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatLog::resizeEvent(QResizeEvent* ev)
|
void ChatLog::resizeEvent(QResizeEvent* ev)
|
||||||
{
|
{
|
||||||
if(!workerTimer->isActive())
|
startResizeWorker();
|
||||||
{
|
|
||||||
workerStb = stickToBottom();
|
|
||||||
workerLastIndex = 0;
|
|
||||||
workerDy = 0.0;
|
|
||||||
workerTimer->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool stb = stickToBottom();
|
|
||||||
|
|
||||||
QGraphicsView::resizeEvent(ev);
|
QGraphicsView::resizeEvent(ev);
|
||||||
updateVisibleLines();
|
|
||||||
updateMultiSelectionRect();
|
|
||||||
updateTypingNotification();
|
|
||||||
|
|
||||||
if(stb)
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatLog::updateMultiSelectionRect()
|
void ChatLog::updateMultiSelectionRect()
|
||||||
|
@ -692,6 +627,15 @@ void ChatLog::updateTypingNotification()
|
||||||
notification->layout(useableWidth(), QPointF(0.0, posY));
|
notification->layout(useableWidth(), QPointF(0.0, posY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatLog::updateBusyNotification()
|
||||||
|
{
|
||||||
|
if(busyNotification.get())
|
||||||
|
{
|
||||||
|
//repoisition the busy notification (centered)
|
||||||
|
busyNotification->layout(useableWidth(), getVisibleRect().topLeft() + QPointF(0, getVisibleRect().height()/2.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ChatLine::Ptr ChatLog::findLineByPosY(qreal yPos) const
|
ChatLine::Ptr ChatLog::findLineByPosY(qreal yPos) const
|
||||||
{
|
{
|
||||||
auto itr = std::lower_bound(lines.cbegin(), lines.cend(), yPos, ChatLine::lessThanBSRectBottom);
|
auto itr = std::lower_bound(lines.cbegin(), lines.cend(), yPos, ChatLine::lessThanBSRectBottom);
|
||||||
|
@ -728,3 +672,40 @@ void ChatLog::onSelectionTimerTimeout()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatLog::onWorkerTimeout()
|
||||||
|
{
|
||||||
|
// Fairly arbitrary but
|
||||||
|
// large values will make the UI unresponsive
|
||||||
|
const int stepSize = 400;
|
||||||
|
|
||||||
|
layout(workerLastIndex, workerLastIndex+stepSize, useableWidth());
|
||||||
|
workerLastIndex += stepSize;
|
||||||
|
|
||||||
|
// done?
|
||||||
|
if(workerLastIndex >= lines.size())
|
||||||
|
{
|
||||||
|
workerTimer->stop();
|
||||||
|
|
||||||
|
// switch back to the scene containing the chat messages
|
||||||
|
setScene(scene);
|
||||||
|
|
||||||
|
// make sure everything gets updated
|
||||||
|
updateSceneRect();
|
||||||
|
checkVisibility();
|
||||||
|
updateTypingNotification();
|
||||||
|
updateMultiSelectionRect();
|
||||||
|
|
||||||
|
// scroll
|
||||||
|
if(workerStb)
|
||||||
|
scrollToBottom();
|
||||||
|
else
|
||||||
|
scrollToLine(workerAnchorLine);
|
||||||
|
|
||||||
|
// don't keep a Ptr to the anchor line
|
||||||
|
workerAnchorLine = ChatLine::Ptr();
|
||||||
|
|
||||||
|
// hidden during busy screen
|
||||||
|
verticalScrollBar()->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -43,8 +43,10 @@ public:
|
||||||
void clearSelection();
|
void clearSelection();
|
||||||
void clear();
|
void clear();
|
||||||
void copySelectedText() const;
|
void copySelectedText() const;
|
||||||
|
void setBusyNotification(ChatLine::Ptr notification);
|
||||||
void setTypingNotification(ChatLine::Ptr notification);
|
void setTypingNotification(ChatLine::Ptr notification);
|
||||||
void setTypingNotificationVisible(bool visible);
|
void setTypingNotificationVisible(bool visible);
|
||||||
|
void scrollToLine(ChatLine::Ptr line);
|
||||||
QString getSelectedText() const;
|
QString getSelectedText() const;
|
||||||
QString toPlainText() const;
|
QString toPlainText() const;
|
||||||
|
|
||||||
|
@ -58,7 +60,7 @@ protected:
|
||||||
QRect getVisibleRect() const;
|
QRect getVisibleRect() const;
|
||||||
ChatLineContent* getContentFromPos(QPointF scenePos) const;
|
ChatLineContent* getContentFromPos(QPointF scenePos) const;
|
||||||
|
|
||||||
qreal layout(int start, int end, qreal width);
|
void layout(int start, int end, qreal width);
|
||||||
bool isOverSelection(QPointF scenePos) const;
|
bool isOverSelection(QPointF scenePos) const;
|
||||||
bool stickToBottom() const;
|
bool stickToBottom() const;
|
||||||
|
|
||||||
|
@ -66,9 +68,9 @@ protected:
|
||||||
|
|
||||||
void reposition(int start, int end, qreal deltaY);
|
void reposition(int start, int end, qreal deltaY);
|
||||||
void updateSceneRect();
|
void updateSceneRect();
|
||||||
void updateVisibleLines();
|
|
||||||
void checkVisibility();
|
void checkVisibility();
|
||||||
void scrollToBottom();
|
void scrollToBottom();
|
||||||
|
void startResizeWorker();
|
||||||
|
|
||||||
virtual void mouseDoubleClickEvent(QMouseEvent* ev);
|
virtual void mouseDoubleClickEvent(QMouseEvent* ev);
|
||||||
virtual void mousePressEvent(QMouseEvent* ev);
|
virtual void mousePressEvent(QMouseEvent* ev);
|
||||||
|
@ -79,11 +81,13 @@ protected:
|
||||||
|
|
||||||
void updateMultiSelectionRect();
|
void updateMultiSelectionRect();
|
||||||
void updateTypingNotification();
|
void updateTypingNotification();
|
||||||
|
void updateBusyNotification();
|
||||||
|
|
||||||
ChatLine::Ptr findLineByPosY(qreal yPos) const;
|
ChatLine::Ptr findLineByPosY(qreal yPos) const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onSelectionTimerTimeout();
|
void onSelectionTimerTimeout();
|
||||||
|
void onWorkerTimeout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum SelectionMode {
|
enum SelectionMode {
|
||||||
|
@ -99,9 +103,11 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
QGraphicsScene* scene = nullptr;
|
QGraphicsScene* scene = nullptr;
|
||||||
|
QGraphicsScene* busyScene = nullptr;
|
||||||
QVector<ChatLine::Ptr> lines;
|
QVector<ChatLine::Ptr> lines;
|
||||||
QList<ChatLine::Ptr> visibleLines;
|
QList<ChatLine::Ptr> visibleLines;
|
||||||
ChatLine::Ptr typingNotification;
|
ChatLine::Ptr typingNotification;
|
||||||
|
ChatLine::Ptr busyNotification;
|
||||||
|
|
||||||
// selection
|
// selection
|
||||||
int selClickedRow = -1; //These 4 are only valid while selectionMode != None
|
int selClickedRow = -1; //These 4 are only valid while selectionMode != None
|
||||||
|
@ -117,8 +123,8 @@ private:
|
||||||
|
|
||||||
//worker vars
|
//worker vars
|
||||||
int workerLastIndex = 0;
|
int workerLastIndex = 0;
|
||||||
qreal workerDy = 0;
|
|
||||||
bool workerStb = false;
|
bool workerStb = false;
|
||||||
|
ChatLine::Ptr workerAnchorLine;
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
QAction* copyAction = nullptr;
|
QAction* copyAction = nullptr;
|
||||||
|
|
|
@ -101,8 +101,19 @@ ChatMessage::Ptr ChatMessage::createTypingNotification()
|
||||||
{
|
{
|
||||||
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
||||||
|
|
||||||
|
// Note: "[user]..." is just a placeholder. The actual text is set in ChatForm::setFriendTyping()
|
||||||
msg->addColumn(new NotificationIcon(QSizeF(18, 18)), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
msg->addColumn(new NotificationIcon(QSizeF(18, 18)), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||||
msg->addColumn(new Text("%1 ...", Style::getFont(Style::Big), false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left));
|
msg->addColumn(new Text("[user]...", Style::getFont(Style::Big), false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left));
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatMessage::Ptr ChatMessage::createBusyNotification()
|
||||||
|
{
|
||||||
|
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
||||||
|
|
||||||
|
// TODO: Bigger font
|
||||||
|
msg->addColumn(new Text(QObject::tr("Busy..."), Style::getFont(Style::ExtraBig), false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Center));
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ public:
|
||||||
static ChatMessage::Ptr createChatInfoMessage(const QString& rawMessage, SystemMessageType type, const QDateTime& date);
|
static ChatMessage::Ptr createChatInfoMessage(const QString& rawMessage, SystemMessageType type, const QDateTime& date);
|
||||||
static ChatMessage::Ptr createFileTransferMessage(const QString& sender, ToxFile file, bool isMe, const QDateTime& date);
|
static ChatMessage::Ptr createFileTransferMessage(const QString& sender, ToxFile file, bool isMe, const QDateTime& date);
|
||||||
static ChatMessage::Ptr createTypingNotification();
|
static ChatMessage::Ptr createTypingNotification();
|
||||||
|
static ChatMessage::Ptr createBusyNotification();
|
||||||
|
|
||||||
void markAsSent(const QDateTime& time);
|
void markAsSent(const QDateTime& time);
|
||||||
QString toString() const;
|
QString toString() const;
|
||||||
|
|
|
@ -56,6 +56,7 @@ GenericChatForm::GenericChatForm(QWidget *parent)
|
||||||
headTextLayout = new QVBoxLayout();
|
headTextLayout = new QVBoxLayout();
|
||||||
|
|
||||||
chatWidget = new ChatLog(this);
|
chatWidget = new ChatLog(this);
|
||||||
|
chatWidget->setBusyNotification(ChatMessage::createBusyNotification());
|
||||||
|
|
||||||
msgEdit = new ChatTextEdit();
|
msgEdit = new ChatTextEdit();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user