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"