File indexing completed on 2024-04-28 05:50:52

0001 /*
0002     SPDX-FileCopyrightText: 2020-2020 Gustavo Carneiro <gcarneiroa@hotmail.com>
0003     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
0004     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 // Own
0010 #include "TerminalScrollBar.h"
0011 
0012 // Konsole
0013 #include "../characters/Character.h"
0014 #include "TerminalDisplay.h"
0015 #include "TerminalFonts.h"
0016 
0017 // KDE
0018 
0019 // Qt
0020 #include <QGuiApplication>
0021 #include <QProxyStyle>
0022 #include <QRect>
0023 
0024 namespace Konsole
0025 {
0026 TerminalScrollBar::TerminalScrollBar(QWidget *parent)
0027     : QScrollBar(parent)
0028 {
0029     connect(this, &QScrollBar::valueChanged, this, &TerminalScrollBar::scrollBarPositionChanged);
0030 }
0031 
0032 void TerminalScrollBar::setScrollBarPosition(Enum::ScrollBarPositionEnum position)
0033 {
0034     if (_scrollbarLocation == position) {
0035         return;
0036     }
0037 
0038     _scrollbarLocation = position;
0039     applyScrollBarPosition(true);
0040 }
0041 
0042 void TerminalScrollBar::setScroll(int cursor, int slines)
0043 {
0044     const auto display = qobject_cast<TerminalDisplay *>(this->parent());
0045     // update _scrollBar if the range or value has changed,
0046     // otherwise return
0047     //
0048     // setting the range or value of a _scrollBar will always trigger
0049     // a repaint, so it should be avoided if it is not necessary
0050     if (this->minimum() == 0 && this->maximum() == (slines - display->lines()) && this->value() == cursor) {
0051         return;
0052     }
0053 
0054     disconnect(this, &QScrollBar::valueChanged, this, &TerminalScrollBar::scrollBarPositionChanged);
0055     setRange(0, slines - display->lines());
0056     setSingleStep(1);
0057     setPageStep(display->lines());
0058     setValue(cursor);
0059     connect(this, &QScrollBar::valueChanged, this, &TerminalScrollBar::scrollBarPositionChanged);
0060 }
0061 
0062 void TerminalScrollBar::setScrollFullPage(bool fullPage)
0063 {
0064     _scrollFullPage = fullPage;
0065 }
0066 
0067 bool TerminalScrollBar::scrollFullPage() const
0068 {
0069     return _scrollFullPage;
0070 }
0071 
0072 void TerminalScrollBar::setHighlightScrolledLines(bool highlight)
0073 {
0074     _highlightScrolledLines.setEnabled(highlight);
0075     _highlightScrolledLines.setTimer(this);
0076 }
0077 
0078 bool TerminalScrollBar::alternateScrolling() const
0079 {
0080     return _alternateScrolling;
0081 }
0082 
0083 void TerminalScrollBar::setAlternateScrolling(bool enable)
0084 {
0085     _alternateScrolling = enable;
0086 }
0087 
0088 void TerminalScrollBar::scrollBarPositionChanged(int)
0089 {
0090     const auto display = qobject_cast<TerminalDisplay *>(this->parent());
0091 
0092     if (display->screenWindow().isNull()) {
0093         return;
0094     }
0095 
0096     display->screenWindow()->scrollTo(this->value());
0097 
0098     // if the thumb has been moved to the bottom of the _scrollBar then set
0099     // the display to automatically track new output,
0100     // that is, scroll down automatically
0101     // to how new _lines as they are added
0102     const bool atEndOfOutput = (this->value() == this->maximum());
0103     display->screenWindow()->setTrackOutput(atEndOfOutput);
0104 
0105     display->updateImage();
0106 }
0107 
0108 void TerminalScrollBar::highlightScrolledLinesEvent()
0109 {
0110     const auto display = qobject_cast<TerminalDisplay *>(this->parent());
0111     display->update(_highlightScrolledLines.rect());
0112 }
0113 
0114 void TerminalScrollBar::applyScrollBarPosition(bool propagate)
0115 {
0116     setHidden(_scrollbarLocation == Enum::ScrollBarHidden);
0117 
0118     if (propagate) {
0119         const auto display = qobject_cast<TerminalDisplay *>(this->parent());
0120         display->propagateSize();
0121         display->update();
0122     }
0123 }
0124 
0125 // scrolls the image by 'lines', down if lines > 0 or up otherwise.
0126 //
0127 // the terminal emulation keeps track of the scrolling of the character
0128 // image as it receives input, and when the view is updated, it calls scrollImage()
0129 // with the final scroll amount.  this improves performance because scrolling the
0130 // display is much cheaper than re-rendering all the text for the
0131 // part of the image which has moved up or down.
0132 // Instead only new lines have to be drawn
0133 void TerminalScrollBar::scrollImage(int lines, const QRect &screenWindowRegion, Character *image, int imageSize)
0134 {
0135     // return if there is nothing to do
0136     if ((lines == 0) || (image == nullptr)) {
0137         return;
0138     }
0139 
0140     const auto display = qobject_cast<TerminalDisplay *>(this->parent());
0141     // constrain the region to the display
0142     // the bottom of the region is capped to the number of lines in the display's
0143     // internal image - 2, so that the height of 'region' is strictly less
0144     // than the height of the internal image.
0145     QRect region = screenWindowRegion;
0146     region.setBottom(qMin(region.bottom(), display->lines() - 2));
0147 
0148     // return if there is nothing to do
0149     if (!region.isValid() || (region.top() + abs(lines)) >= region.bottom() || display->lines() <= region.bottom()) {
0150         return;
0151     }
0152 
0153     // Note:  With Qt 4.4 the left edge of the scrolled area must be at 0
0154     // to get the correct (newly exposed) part of the widget repainted.
0155     //
0156     // The right edge must be before the left edge of the scroll bar to
0157     // avoid triggering a repaint of the entire widget, the distance is
0158     // given by SCROLLBAR_CONTENT_GAP
0159     //
0160     // Set the QT_FLUSH_PAINT environment variable to '1' before starting the
0161     // application to monitor repainting.
0162     //
0163     const int scrollBarWidth = this->isHidden() ? 0 : this->width();
0164     const int SCROLLBAR_CONTENT_GAP = 1;
0165     QRect scrollRect;
0166     if (_scrollbarLocation == Enum::ScrollBarLeft) {
0167         scrollRect.setLeft(scrollBarWidth + SCROLLBAR_CONTENT_GAP
0168                            + (_highlightScrolledLines.isEnabled() ? _highlightScrolledLines.HIGHLIGHT_SCROLLED_LINES_WIDTH : 0));
0169         scrollRect.setRight(display->width());
0170     } else {
0171         scrollRect.setLeft(_highlightScrolledLines.isEnabled() ? _highlightScrolledLines.HIGHLIGHT_SCROLLED_LINES_WIDTH : 0);
0172 
0173         scrollRect.setRight(display->width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
0174     }
0175     void *firstCharPos = &image[region.top() * display->columns()];
0176     void *lastCharPos = &image[(region.top() + abs(lines)) * display->columns()];
0177 
0178     const int top = display->contentRect().top() + (region.top() * display->terminalFont()->fontHeight());
0179     const int linesToMove = region.height() - abs(lines);
0180     const int bytesToMove = linesToMove * display->columns() * sizeof(Character);
0181 
0182     Q_ASSERT(linesToMove > 0);
0183     Q_ASSERT(bytesToMove > 0);
0184 
0185     scrollRect.setTop(lines > 0 ? top : top + abs(lines) * display->terminalFont()->fontHeight());
0186     scrollRect.setHeight(linesToMove * display->terminalFont()->fontHeight());
0187 
0188     if (!scrollRect.isValid() || scrollRect.isEmpty()) {
0189         return;
0190     }
0191 
0192     // scroll internal image
0193     if (lines > 0) {
0194         // check that the memory areas that we are going to move are valid
0195         Q_ASSERT((char *)lastCharPos + bytesToMove < (char *)(image + (display->lines() * display->columns())));
0196         Q_ASSERT((lines * display->columns()) < imageSize);
0197 
0198         // scroll internal image down
0199         memmove(firstCharPos, lastCharPos, bytesToMove);
0200     } else {
0201         // check that the memory areas that we are going to move are valid
0202         Q_ASSERT((char *)firstCharPos + bytesToMove < (char *)(image + (display->lines() * display->columns())));
0203 
0204         // scroll internal image up
0205         memmove(lastCharPos, firstCharPos, bytesToMove);
0206     }
0207 
0208     // scroll the display vertically to match internal _image
0209     display->scroll(0, display->terminalFont()->fontHeight() * (-lines), scrollRect);
0210 }
0211 
0212 void TerminalScrollBar::changeEvent(QEvent *e)
0213 {
0214     if (e->type() == QEvent::StyleChange) {
0215         updatePalette(_backgroundMatchingPalette);
0216     }
0217     QScrollBar::changeEvent(e);
0218 }
0219 
0220 void TerminalScrollBar::updatePalette(const QPalette &pal)
0221 {
0222     _backgroundMatchingPalette = pal;
0223 
0224     auto proxyStyle = qobject_cast<const QProxyStyle *>(style());
0225     const QStyle *appStyle = proxyStyle ? proxyStyle->baseStyle() : style();
0226 
0227     // Scrollbars in widget styles like Fusion or Plastique do not work well with custom
0228     // scrollbar coloring, in particular in conjunction with light terminal background colors.
0229     // Use custom colors only for widget styles matched by the allowlist below, otherwise
0230     // fall back to generic widget colors.
0231     if (appStyle->objectName() == QLatin1String("breeze")) {
0232         setPalette(_backgroundMatchingPalette);
0233     } else {
0234         setPalette(QGuiApplication::palette());
0235     }
0236 }
0237 
0238 } // namespace Konsole
0239 
0240 #include "moc_TerminalScrollBar.cpp"