You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
8.3 KiB
245 lines
8.3 KiB
/* |
|
SPDX-FileCopyrightText: 2020-2020 Gustavo Carneiro <gcarneiroa@hotmail.com> |
|
SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com> |
|
SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
// Own |
|
#include "TerminalScrollBar.h" |
|
|
|
// Konsole |
|
#include "../characters/Character.h" |
|
#include "TerminalDisplay.h" |
|
#include "TerminalFonts.h" |
|
#include "session/SessionController.h" |
|
|
|
// KDE |
|
#include <KMessageWidget> |
|
|
|
// Qt |
|
#include <QGuiApplication> |
|
#include <QLabel> |
|
#include <QProxyStyle> |
|
#include <QRect> |
|
#include <QTimer> |
|
|
|
namespace Konsole |
|
{ |
|
TerminalScrollBar::TerminalScrollBar(QWidget *parent) |
|
: QScrollBar(parent) |
|
, _scrollFullPage(false) |
|
, _alternateScrolling(false) |
|
, _scrollbarLocation(Enum::ScrollBarRight) |
|
{ |
|
connect(this, &QScrollBar::valueChanged, this, &TerminalScrollBar::scrollBarPositionChanged); |
|
} |
|
|
|
void TerminalScrollBar::setScrollBarPosition(Enum::ScrollBarPositionEnum position) |
|
{ |
|
if (_scrollbarLocation == position) { |
|
return; |
|
} |
|
|
|
_scrollbarLocation = position; |
|
applyScrollBarPosition(true); |
|
} |
|
|
|
void TerminalScrollBar::setScroll(int cursor, int slines) |
|
{ |
|
const auto display = qobject_cast<TerminalDisplay *>(this->parent()); |
|
// update _scrollBar if the range or value has changed, |
|
// otherwise return |
|
// |
|
// setting the range or value of a _scrollBar will always trigger |
|
// a repaint, so it should be avoided if it is not necessary |
|
if (this->minimum() == 0 && this->maximum() == (slines - display->lines()) && this->value() == cursor) { |
|
return; |
|
} |
|
|
|
disconnect(this, &QScrollBar::valueChanged, this, &TerminalScrollBar::scrollBarPositionChanged); |
|
setRange(0, slines - display->lines()); |
|
setSingleStep(1); |
|
setPageStep(display->lines()); |
|
setValue(cursor); |
|
connect(this, &QScrollBar::valueChanged, this, &TerminalScrollBar::scrollBarPositionChanged); |
|
} |
|
|
|
void TerminalScrollBar::setScrollFullPage(bool fullPage) |
|
{ |
|
_scrollFullPage = fullPage; |
|
} |
|
|
|
bool TerminalScrollBar::scrollFullPage() const |
|
{ |
|
return _scrollFullPage; |
|
} |
|
|
|
void TerminalScrollBar::setHighlightScrolledLines(bool highlight) |
|
{ |
|
_highlightScrolledLines.setEnabled(highlight); |
|
_highlightScrolledLines.setTimer(this); |
|
} |
|
|
|
bool TerminalScrollBar::alternateScrolling() const |
|
{ |
|
return _alternateScrolling; |
|
} |
|
|
|
void TerminalScrollBar::setAlternateScrolling(bool enable) |
|
{ |
|
_alternateScrolling = enable; |
|
} |
|
|
|
void TerminalScrollBar::scrollBarPositionChanged(int) |
|
{ |
|
const auto display = qobject_cast<TerminalDisplay *>(this->parent()); |
|
|
|
if (display->screenWindow().isNull()) { |
|
return; |
|
} |
|
|
|
display->screenWindow()->scrollTo(this->value()); |
|
|
|
// if the thumb has been moved to the bottom of the _scrollBar then set |
|
// the display to automatically track new output, |
|
// that is, scroll down automatically |
|
// to how new _lines as they are added |
|
const bool atEndOfOutput = (this->value() == this->maximum()); |
|
display->screenWindow()->setTrackOutput(atEndOfOutput); |
|
|
|
display->updateImage(); |
|
} |
|
|
|
void TerminalScrollBar::highlightScrolledLinesEvent() |
|
{ |
|
const auto display = qobject_cast<TerminalDisplay *>(this->parent()); |
|
display->update(_highlightScrolledLines.rect()); |
|
} |
|
|
|
void TerminalScrollBar::applyScrollBarPosition(bool propagate) |
|
{ |
|
setHidden(_scrollbarLocation == Enum::ScrollBarHidden); |
|
|
|
if (propagate) { |
|
const auto display = qobject_cast<TerminalDisplay *>(this->parent()); |
|
display->propagateSize(); |
|
display->update(); |
|
} |
|
} |
|
|
|
// scrolls the image by 'lines', down if lines > 0 or up otherwise. |
|
// |
|
// the terminal emulation keeps track of the scrolling of the character |
|
// image as it receives input, and when the view is updated, it calls scrollImage() |
|
// with the final scroll amount. this improves performance because scrolling the |
|
// display is much cheaper than re-rendering all the text for the |
|
// part of the image which has moved up or down. |
|
// Instead only new lines have to be drawn |
|
void TerminalScrollBar::scrollImage(int lines, const QRect &screenWindowRegion, Character *image, int imageSize) |
|
{ |
|
// return if there is nothing to do |
|
if ((lines == 0) || (image == nullptr)) { |
|
return; |
|
} |
|
|
|
const auto display = qobject_cast<TerminalDisplay *>(this->parent()); |
|
// constrain the region to the display |
|
// the bottom of the region is capped to the number of lines in the display's |
|
// internal image - 2, so that the height of 'region' is strictly less |
|
// than the height of the internal image. |
|
QRect region = screenWindowRegion; |
|
region.setBottom(qMin(region.bottom(), display->lines() - 2)); |
|
|
|
// return if there is nothing to do |
|
if (!region.isValid() || (region.top() + abs(lines)) >= region.bottom() || display->lines() <= region.bottom()) { |
|
return; |
|
} |
|
|
|
// Note: With Qt 4.4 the left edge of the scrolled area must be at 0 |
|
// to get the correct (newly exposed) part of the widget repainted. |
|
// |
|
// The right edge must be before the left edge of the scroll bar to |
|
// avoid triggering a repaint of the entire widget, the distance is |
|
// given by SCROLLBAR_CONTENT_GAP |
|
// |
|
// Set the QT_FLUSH_PAINT environment variable to '1' before starting the |
|
// application to monitor repainting. |
|
// |
|
const int scrollBarWidth = this->isHidden() ? 0 : this->width(); |
|
const int SCROLLBAR_CONTENT_GAP = 1; |
|
QRect scrollRect; |
|
if (_scrollbarLocation == Enum::ScrollBarLeft) { |
|
scrollRect.setLeft(scrollBarWidth + SCROLLBAR_CONTENT_GAP |
|
+ (_highlightScrolledLines.isEnabled() ? _highlightScrolledLines.HIGHLIGHT_SCROLLED_LINES_WIDTH : 0)); |
|
scrollRect.setRight(display->width()); |
|
} else { |
|
scrollRect.setLeft(_highlightScrolledLines.isEnabled() ? _highlightScrolledLines.HIGHLIGHT_SCROLLED_LINES_WIDTH : 0); |
|
|
|
scrollRect.setRight(display->width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP); |
|
} |
|
void *firstCharPos = &image[region.top() * display->columns()]; |
|
void *lastCharPos = &image[(region.top() + abs(lines)) * display->columns()]; |
|
|
|
const int top = display->contentRect().top() + (region.top() * display->terminalFont()->fontHeight()); |
|
const int linesToMove = region.height() - abs(lines); |
|
const int bytesToMove = linesToMove * display->columns() * sizeof(Character); |
|
|
|
Q_ASSERT(linesToMove > 0); |
|
Q_ASSERT(bytesToMove > 0); |
|
|
|
scrollRect.setTop(lines > 0 ? top : top + abs(lines) * display->terminalFont()->fontHeight()); |
|
scrollRect.setHeight(linesToMove * display->terminalFont()->fontHeight()); |
|
|
|
if (!scrollRect.isValid() || scrollRect.isEmpty()) { |
|
return; |
|
} |
|
|
|
// scroll internal image |
|
if (lines > 0) { |
|
// check that the memory areas that we are going to move are valid |
|
Q_ASSERT((char *)lastCharPos + bytesToMove < (char *)(image + (display->lines() * display->columns()))); |
|
Q_ASSERT((lines * display->columns()) < imageSize); |
|
|
|
// scroll internal image down |
|
memmove(firstCharPos, lastCharPos, bytesToMove); |
|
} else { |
|
// check that the memory areas that we are going to move are valid |
|
Q_ASSERT((char *)firstCharPos + bytesToMove < (char *)(image + (display->lines() * display->columns()))); |
|
|
|
// scroll internal image up |
|
memmove(lastCharPos, firstCharPos, bytesToMove); |
|
} |
|
|
|
// scroll the display vertically to match internal _image |
|
display->scroll(0, display->terminalFont()->fontHeight() * (-lines), scrollRect); |
|
} |
|
|
|
void TerminalScrollBar::changeEvent(QEvent *e) |
|
{ |
|
if (e->type() == QEvent::StyleChange) { |
|
updatePalette(_backgroundMatchingPalette); |
|
} |
|
QScrollBar::changeEvent(e); |
|
} |
|
|
|
void TerminalScrollBar::updatePalette(const QPalette &pal) |
|
{ |
|
_backgroundMatchingPalette = pal; |
|
|
|
auto proxyStyle = qobject_cast<const QProxyStyle *>(style()); |
|
const QStyle *appStyle = proxyStyle ? proxyStyle->baseStyle() : style(); |
|
|
|
// Scrollbars in widget styles like Fusion or Plastique do not work well with custom |
|
// scrollbar coloring, in particular in conjunction with light terminal background colors. |
|
// Use custom colors only for widget styles matched by the allowlist below, otherwise |
|
// fall back to generic widget colors. |
|
if (appStyle->objectName() == QLatin1String("breeze")) { |
|
setPalette(_backgroundMatchingPalette); |
|
} else { |
|
setPalette(QGuiApplication::palette()); |
|
} |
|
} |
|
|
|
} // namespace Konsole
|
|
|