1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00
qTox/src/chatlog/chatlog.cpp

711 lines
18 KiB
C++
Raw Normal View History

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>
#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)
{
2015-01-15 01:22:42 +08:00
// Create the scene
2014-11-12 21:11:25 +08:00
scene = new QGraphicsScene(this);
2015-01-15 01:22:42 +08:00
scene->setItemIndexMethod(QGraphicsScene::NoIndex); //Bsp-Tree is actually slower in this case
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);
setAlignment(Qt::AlignTop | Qt::AlignLeft);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setDragMode(QGraphicsView::NoDrag);
2015-01-04 04:19:52 +08:00
setViewportUpdateMode(BoundingRectViewportUpdate);
2015-01-02 20:04:40 +08:00
setAcceptDrops(false);
setContextMenuPolicy(Qt::CustomContextMenu);
2014-11-12 21:11:25 +08:00
2015-01-15 01:22:42 +08:00
// The selection rect for multi-line selection
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
2015-01-15 01:22:42 +08:00
// copy action (ie. Ctrl+C)
2014-11-12 21:11:25 +08:00
copyAction = new QAction(this);
copyAction->setShortcut(QKeySequence::Copy);
addAction(copyAction);
connect(copyAction, &QAction::triggered, this, [this](bool)
2014-11-12 21:11:25 +08:00
{
copySelectedText();
});
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);
selectionTimer->setInterval(1000/60);
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
workerTimer = new QTimer(this);
workerTimer->setSingleShot(false);
workerTimer->setInterval(100);
connect(workerTimer, &QTimer::timeout, this, [this] {
2015-01-11 05:21:33 +08:00
const int stepSize = 400;
workerDy += layout(workerLastIndex, workerLastIndex+stepSize, useableWidth());
if(!visibleLines.isEmpty())
{
int firstVisLineIndex = visibleLines.first()->getRowIndex();
int delta = firstVisLineIndex - workerLastIndex;
if(delta > 0 && delta < stepSize)
{
workerLastIndex += delta+1;
if(!workerStb)
verticalScrollBar()->setValue(verticalScrollBar()->value() - workerDy);
workerDy = 0.0;
checkVisibility();
}
else
2015-01-11 05:21:33 +08:00
{
workerLastIndex += stepSize;
2015-01-11 05:21:33 +08:00
}
}
else
workerLastIndex += stepSize;
if(workerLastIndex >= lines.size())
{
workerTimer->stop();
workerLastIndex = 0;
workerDy = 0.0;
if(stickToBottom())
scrollToBottom();
}
});
2014-11-12 21:11:25 +08:00
}
void ChatLog::clearSelection()
{
if(selectionMode == None)
return;
2015-01-15 01:22:42 +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;
selectionMode = None;
updateMultiSelectionRect();
2014-11-12 21:11:25 +08:00
}
QRect ChatLog::getVisibleRect() const
{
return mapToScene(viewport()->rect()).boundingRect().toRect();
2014-11-12 21:11:25 +08:00
}
void ChatLog::updateSceneRect()
{
setSceneRect(calculateSceneRect());
2014-11-12 21:11:25 +08:00
}
qreal ChatLog::layout(int start, int end, qreal width)
2014-11-12 21:11:25 +08:00
{
if(lines.empty())
return false;
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.
if(start - 1 >= 0)
h = lines[start - 1]->boundingSceneRect().bottom() + lineSpacing;
start = clamp<int>(start, 0, lines.size());
end = clamp<int>(end + 1, 0, lines.size());
2014-11-12 21:11:25 +08:00
2015-01-06 17:37:07 +08:00
qreal deltaY = 0.0;
2014-11-12 21:11:25 +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
qreal oldHeight = l->boundingSceneRect().height();
l->layout(width, QPointF(0.0, h));
2014-11-12 21:11:25 +08:00
if(oldHeight != l->boundingSceneRect().height())
2015-01-06 17:37:07 +08:00
deltaY += oldHeight - l->boundingSceneRect().height();
2014-11-12 21:11:25 +08:00
h += l->boundingSceneRect().height() + lineSpacing;
}
2015-01-06 17:37:07 +08:00
return deltaY;
2014-11-12 21:11:25 +08:00
}
2015-01-15 03:13:41 +08:00
void ChatLog::updateVisibleLines()
2014-11-12 21:11:25 +08:00
{
checkVisibility();
if(visibleLines.empty())
return;
bool stb = stickToBottom();
2014-11-12 21:11:25 +08:00
auto oldUpdateMode = viewportUpdateMode();
setViewportUpdateMode(NoViewportUpdate);
2015-01-15 01:22:42 +08:00
// 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;
2014-11-12 21:11:25 +08:00
do
{
repos = 0;
2014-11-12 21:11:25 +08:00
if(!visibleLines.empty())
{
2014-11-12 21:11:25 +08:00
repos = layout(visibleLines.first()->getRowIndex(), visibleLines.last()->getRowIndex(), useableWidth());
reposition(visibleLines.last()->getRowIndex()+1, visibleLines.last()->getRowIndex()+10, -repos);
verticalScrollBar()->setValue(verticalScrollBar()->value() - repos);
}
2014-11-12 21:11:25 +08:00
checkVisibility();
}
while(repos != 0);
2014-11-12 21:11:25 +08:00
checkVisibility();
setViewportUpdateMode(oldUpdateMode);
if(stb)
scrollToBottom();
2014-11-12 21:11:25 +08:00
}
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);
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());
ChatLine::Ptr line = findLineByPosY(scenePos.y());
2014-11-12 21:11:25 +08:00
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();
}
else if(line.get())
{
selClickedRow = line->getRowIndex();
selFirstRow = selClickedRow;
selLastRow = selClickedRow;
selectionMode = Multi;
}
2014-11-12 21:11:25 +08:00
}
2015-01-14 06:59:38 +08:00
if(selectionMode != None)
2015-01-02 18:25:07 +08:00
{
ChatLineContent* content = getContentFromPos(scenePos);
ChatLine::Ptr line = findLineByPosY(scenePos.y());
if(!content && !line.get())
return;
int row;
2014-11-12 21:11:25 +08:00
2015-01-02 18:25:07 +08:00
if(content)
{
row = content->getRow();
2015-01-02 18:25:07 +08:00
int col = content->getColumn();
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 if(col != selClickedCol)
2015-01-02 18:25:07 +08:00
{
selectionMode = Multi;
2014-11-12 21:11:25 +08:00
2015-01-02 18:25:07 +08:00
lines[selClickedRow]->selectionCleared();
}
}
else if(line.get())
{
row = line->getRowIndex();
if(row != selClickedRow)
{
selectionMode = Multi;
2014-11-12 21:11:25 +08:00
lines[selClickedRow]->selectionCleared();
2015-01-02 18:25:07 +08:00
}
2014-12-09 20:17:08 +08:00
}
if(row >= selClickedRow)
selLastRow = row;
if(row <= selClickedRow)
selFirstRow = row;
updateMultiSelectionRect();
2014-11-12 21:11:25 +08:00
}
}
}
//Much faster than QGraphicsScene::itemAt()!
2014-11-12 21:11:25 +08:00
ChatLineContent* ChatLog::getContentFromPos(QPointF scenePos) const
{
2015-01-07 00:47:57 +08:00
if(lines.empty())
return nullptr;
2014-11-12 21:11:25 +08:00
auto itr = std::lower_bound(lines.cbegin(), lines.cend(), scenePos.y(), ChatLine::lessThanBSRectBottom);
2015-01-07 00:47:57 +08:00
2015-01-14 17:34:52 +08:00
//find content
if(itr != lines.cend() && (*itr)->boundingSceneRect().contains(scenePos))
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
{
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;
}
2015-01-14 06:59:38 +08:00
qreal ChatLog::useableWidth() const
2014-11-12 21:11: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
{
if(lines.isEmpty())
return;
start = clamp<int>(start, 0, lines.size() - 1);
end = clamp<int>(end + 1, 0, lines.size());
2015-01-05 21:06:14 +08:00
for(int i = start; i < end; ++i)
2014-11-12 21:11:25 +08:00
{
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
}
}
void ChatLog::insertChatlineAtBottom(ChatLine::Ptr l)
2014-11-12 21:11:25 +08:00
{
2015-01-05 01:21:35 +08:00
if(!l.get())
return;
2015-01-14 06:59:38 +08:00
bool stickToBtm = stickToBottom();
2014-11-12 21:11:25 +08:00
2015-01-14 06:59:38 +08:00
//insert
2014-11-12 21:11:25 +08:00
l->setRowIndex(lines.size());
2015-01-14 06:59:38 +08:00
l->addToScene(scene);
2014-11-12 21:11:25 +08:00
lines.append(l);
//partial refresh
layout(lines.last()->getRowIndex(), lines.size(), useableWidth());
2014-11-12 21:11:25 +08:00
updateSceneRect();
if(stickToBtm)
scrollToBottom();
checkVisibility();
}
void ChatLog::insertChatlineOnTop(ChatLine::Ptr l)
{
if(!l.get())
return;
2015-01-17 18:31:57 +08:00
insertChatlineOnTop(QList<ChatLine::Ptr>() << l);
}
void ChatLog::insertChatlineOnTop(const QList<ChatLine::Ptr>& newLines)
{
if(newLines.isEmpty())
return;
2015-01-14 06:59:38 +08:00
bool stickToBtm = stickToBottom();
//move all lines down by n
int n = newLines.size();
for(ChatLine::Ptr l : lines)
l->setRowIndex(l->getRowIndex() + n);
//add the new line
for(ChatLine::Ptr l : newLines)
{
l->addToScene(scene);
l->setRowIndex(--n);
lines.prepend(l);
}
//full refresh is required
layout(0, lines.size(), useableWidth());
updateSceneRect();
if(stickToBtm)
scrollToBottom();
checkVisibility();
}
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());
}
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)
2014-12-09 20:17:08 +08:00
{
if(lines[i]->content[1]->getText().isEmpty())
continue;
2014-12-09 20:17:08 +08:00
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();
if(i != selLastRow)
out += "\n";
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
}
2015-01-02 19:04:04 +08:00
QString ChatLog::toPlainText() const
{
QString out;
2015-01-04 20:29:14 +08:00
for(ChatLine::Ptr l : lines)
2015-01-02 19:04:04 +08:00
{
2015-01-05 03:24:56 +08:00
out += QString("|%1 @%2|\n%3\n\n").arg(l->getContent(0)->getText(),l->getContent(2)->getText(),l->getContent(1)->getText());
2015-01-02 19:04:04 +08:00
}
return out;
}
2014-12-14 04:11:03 +08:00
bool ChatLog::isEmpty() const
{
return lines.isEmpty();
}
bool ChatLog::hasTextToBeCopied() const
2014-11-12 21:11:25 +08:00
{
return 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;
}
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);
}
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)
{
if(typingNotification.get() != nullptr)
{
typingNotification->setVisible(visible);
updateTypingNotification();
}
}
2014-11-12 21:11:25 +08:00
void ChatLog::checkVisibility()
{
2015-01-04 04:19:52 +08:00
if(lines.empty())
return;
2015-01-14 17:34:52 +08:00
// find first visible line
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
auto upperBound = std::lower_bound(lines.cbegin(), lines.cend(), getVisibleRect().bottom(), ChatLine::lessThanBSRectTop);
2014-11-12 21:11:25 +08:00
// set visibilty
2015-01-04 20:29:14 +08:00
QList<ChatLine::Ptr> newVisibleLines;
2015-01-14 17:34:52 +08:00
for(auto itr = lowerBound; itr != upperBound; ++itr)
2014-11-12 21:11:25 +08:00
{
newVisibleLines.append(*itr);
if(!visibleLines.contains(*itr))
(*itr)->visibilityChanged(true);
visibleLines.removeOne(*itr);
}
2015-01-14 17:34:52 +08:00
// these lines are no longer visible
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;
// enforce order
std::sort(visibleLines.begin(), visibleLines.end(), ChatLine::lessThanRowIndex);
2014-11-12 21:11:25 +08:00
//if(!visibleLines.empty())
// qDebug() << "visible from " << visibleLines.first()->getRowIndex() << "to " << visibleLines.last()->getRowIndex() << " total " << visibleLines.size();
2014-11-12 21:11:25 +08:00
}
void ChatLog::scrollContentsBy(int dx, int dy)
{
QGraphicsView::scrollContentsBy(dx, dy);
2015-01-15 03:13:41 +08:00
updateVisibleLines();
2014-11-12 21:11:25 +08:00
}
void ChatLog::resizeEvent(QResizeEvent* ev)
{
if(!workerTimer->isActive())
{
workerStb = stickToBottom();
workerLastIndex = 0;
workerDy = 0.0;
workerTimer->start();
}
2014-11-12 21:11:25 +08:00
bool stb = stickToBottom();
QGraphicsView::resizeEvent(ev);
2015-01-15 03:13:41 +08:00
updateVisibleLines();
updateMultiSelectionRect();
2014-11-12 21:11:25 +08:00
if(stb)
scrollToBottom();
}
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
2015-01-10 18:57:46 +08:00
void ChatLog::updateTypingNotification()
{
ChatLine* notification = typingNotification.get();
if(!notification)
return;
qreal posY = 0.0;
if(!lines.empty())
posY = lines.last()->boundingSceneRect().bottom() + lineSpacing;
notification->layout(useableWidth(), QPointF(0.0, posY));
}
ChatLine::Ptr ChatLog::findLineByPosY(qreal yPos) const
{
auto itr = std::lower_bound(lines.cbegin(), lines.cend(), yPos, ChatLine::lessThanBSRectBottom);
if(itr != lines.cend())
return *itr;
return ChatLine::Ptr();
}
QRectF ChatLog::calculateSceneRect() const
{
qreal bottom = (lines.empty() ? 0.0 : lines.last()->boundingSceneRect().bottom());
if(typingNotification.get() != nullptr)
bottom += typingNotification->boundingSceneRect().height() + lineSpacing;
return QRectF(-margins.left(), -margins.top(), useableWidth(), bottom + margins.bottom() + margins.top());
}
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;
}
}