1 /*
2 SPDX-FileCopyrightText: 2020-2020 Gustavo Carneiro <gcarneiroa@hotmail.com>
3 SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
4 SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9 // Own
10 #include "TerminalScrollBar.h"
11
12 // Konsole
13 #include "../characters/Character.h"
14 #include "TerminalDisplay.h"
15 #include "TerminalFonts.h"
16 #include "session/SessionController.h"
17
18 // KDE
19 #include <KMessageWidget>
20
21 // Qt
22 #include <QGuiApplication>
23 #include <QLabel>
24 #include <QProxyStyle>
25 #include <QRect>
26 #include <QTimer>
27
28 namespace Konsole
29 {
TerminalScrollBar(QWidget * parent)30 TerminalScrollBar::TerminalScrollBar(QWidget *parent)
31 : QScrollBar(parent)
32 , _scrollFullPage(false)
33 , _alternateScrolling(false)
34 , _scrollbarLocation(Enum::ScrollBarRight)
35 {
36 connect(this, &QScrollBar::valueChanged, this, &TerminalScrollBar::scrollBarPositionChanged);
37 }
38
setScrollBarPosition(Enum::ScrollBarPositionEnum position)39 void TerminalScrollBar::setScrollBarPosition(Enum::ScrollBarPositionEnum position)
40 {
41 if (_scrollbarLocation == position) {
42 return;
43 }
44
45 _scrollbarLocation = position;
46 applyScrollBarPosition(true);
47 }
48
setScroll(int cursor,int slines)49 void TerminalScrollBar::setScroll(int cursor, int slines)
50 {
51 const auto display = qobject_cast<TerminalDisplay *>(this->parent());
52 // update _scrollBar if the range or value has changed,
53 // otherwise return
54 //
55 // setting the range or value of a _scrollBar will always trigger
56 // a repaint, so it should be avoided if it is not necessary
57 if (this->minimum() == 0 && this->maximum() == (slines - display->lines()) && this->value() == cursor) {
58 return;
59 }
60
61 disconnect(this, &QScrollBar::valueChanged, this, &TerminalScrollBar::scrollBarPositionChanged);
62 setRange(0, slines - display->lines());
63 setSingleStep(1);
64 setPageStep(display->lines());
65 setValue(cursor);
66 connect(this, &QScrollBar::valueChanged, this, &TerminalScrollBar::scrollBarPositionChanged);
67 }
68
setScrollFullPage(bool fullPage)69 void TerminalScrollBar::setScrollFullPage(bool fullPage)
70 {
71 _scrollFullPage = fullPage;
72 }
73
scrollFullPage() const74 bool TerminalScrollBar::scrollFullPage() const
75 {
76 return _scrollFullPage;
77 }
78
setHighlightScrolledLines(bool highlight)79 void TerminalScrollBar::setHighlightScrolledLines(bool highlight)
80 {
81 _highlightScrolledLines.setEnabled(highlight);
82 _highlightScrolledLines.setTimer(this);
83 _highlightScrolledLines.setNeedToClear(true);
84 }
85
alternateScrolling() const86 bool TerminalScrollBar::alternateScrolling() const
87 {
88 return _alternateScrolling;
89 }
90
setAlternateScrolling(bool enable)91 void TerminalScrollBar::setAlternateScrolling(bool enable)
92 {
93 _alternateScrolling = enable;
94 }
95
scrollBarPositionChanged(int)96 void TerminalScrollBar::scrollBarPositionChanged(int)
97 {
98 const auto display = qobject_cast<TerminalDisplay *>(this->parent());
99
100 if (display->screenWindow().isNull()) {
101 return;
102 }
103
104 display->screenWindow()->scrollTo(this->value());
105
106 // if the thumb has been moved to the bottom of the _scrollBar then set
107 // the display to automatically track new output,
108 // that is, scroll down automatically
109 // to how new _lines as they are added
110 const bool atEndOfOutput = (this->value() == this->maximum());
111 display->screenWindow()->setTrackOutput(atEndOfOutput);
112
113 display->updateImage();
114 }
115
highlightScrolledLinesEvent()116 void TerminalScrollBar::highlightScrolledLinesEvent()
117 {
118 const auto display = qobject_cast<TerminalDisplay *>(this->parent());
119 display->update(_highlightScrolledLines.rect());
120 }
121
applyScrollBarPosition(bool propagate)122 void TerminalScrollBar::applyScrollBarPosition(bool propagate)
123 {
124 setHidden(_scrollbarLocation == Enum::ScrollBarHidden);
125
126 if (propagate) {
127 const auto display = qobject_cast<TerminalDisplay *>(this->parent());
128 display->propagateSize();
129 display->update();
130 }
131 }
132
133 // scrolls the image by 'lines', down if lines > 0 or up otherwise.
134 //
135 // the terminal emulation keeps track of the scrolling of the character
136 // image as it receives input, and when the view is updated, it calls scrollImage()
137 // with the final scroll amount. this improves performance because scrolling the
138 // display is much cheaper than re-rendering all the text for the
139 // part of the image which has moved up or down.
140 // Instead only new lines have to be drawn
scrollImage(int lines,const QRect & screenWindowRegion,Character * image,int imageSize)141 void TerminalScrollBar::scrollImage(int lines, const QRect &screenWindowRegion, Character *image, int imageSize)
142 {
143 // return if there is nothing to do
144 if ((lines == 0) || (image == nullptr)) {
145 return;
146 }
147
148 const auto display = qobject_cast<TerminalDisplay *>(this->parent());
149 // constrain the region to the display
150 // the bottom of the region is capped to the number of lines in the display's
151 // internal image - 2, so that the height of 'region' is strictly less
152 // than the height of the internal image.
153 QRect region = screenWindowRegion;
154 region.setBottom(qMin(region.bottom(), display->lines() - 2));
155
156 // return if there is nothing to do
157 if (!region.isValid() || (region.top() + abs(lines)) >= region.bottom() || display->lines() <= region.bottom()) {
158 return;
159 }
160
161 // Note: With Qt 4.4 the left edge of the scrolled area must be at 0
162 // to get the correct (newly exposed) part of the widget repainted.
163 //
164 // The right edge must be before the left edge of the scroll bar to
165 // avoid triggering a repaint of the entire widget, the distance is
166 // given by SCROLLBAR_CONTENT_GAP
167 //
168 // Set the QT_FLUSH_PAINT environment variable to '1' before starting the
169 // application to monitor repainting.
170 //
171 const int scrollBarWidth = this->isHidden() ? 0 : this->width();
172 const int SCROLLBAR_CONTENT_GAP = 1;
173 QRect scrollRect;
174 if (_scrollbarLocation == Enum::ScrollBarLeft) {
175 scrollRect.setLeft(scrollBarWidth + SCROLLBAR_CONTENT_GAP
176 + (_highlightScrolledLines.isEnabled() ? _highlightScrolledLines.HIGHLIGHT_SCROLLED_LINES_WIDTH : 0));
177 scrollRect.setRight(display->width());
178 } else {
179 scrollRect.setLeft(_highlightScrolledLines.isEnabled() ? _highlightScrolledLines.HIGHLIGHT_SCROLLED_LINES_WIDTH : 0);
180
181 scrollRect.setRight(display->width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
182 }
183 void *firstCharPos = &image[region.top() * display->columns()];
184 void *lastCharPos = &image[(region.top() + abs(lines)) * display->columns()];
185
186 const int top = display->contentRect().top() + (region.top() * display->terminalFont()->fontHeight());
187 const int linesToMove = region.height() - abs(lines);
188 const int bytesToMove = linesToMove * display->columns() * sizeof(Character);
189
190 Q_ASSERT(linesToMove > 0);
191 Q_ASSERT(bytesToMove > 0);
192
193 scrollRect.setTop(lines > 0 ? top : top + abs(lines) * display->terminalFont()->fontHeight());
194 scrollRect.setHeight(linesToMove * display->terminalFont()->fontHeight());
195
196 if (!scrollRect.isValid() || scrollRect.isEmpty()) {
197 return;
198 }
199
200 // scroll internal image
201 if (lines > 0) {
202 // check that the memory areas that we are going to move are valid
203 Q_ASSERT((char *)lastCharPos + bytesToMove < (char *)(image + (display->lines() * display->columns())));
204 Q_ASSERT((lines * display->columns()) < imageSize);
205
206 // scroll internal image down
207 memmove(firstCharPos, lastCharPos, bytesToMove);
208 } else {
209 // check that the memory areas that we are going to move are valid
210 Q_ASSERT((char *)firstCharPos + bytesToMove < (char *)(image + (display->lines() * display->columns())));
211
212 // scroll internal image up
213 memmove(lastCharPos, firstCharPos, bytesToMove);
214 }
215
216 // scroll the display vertically to match internal _image
217 display->scroll(0, display->terminalFont()->fontHeight() * (-lines), scrollRect);
218 }
219
changeEvent(QEvent * e)220 void TerminalScrollBar::changeEvent(QEvent *e)
221 {
222 if (e->type() == QEvent::StyleChange) {
223 updatePalette(_backgroundMatchingPalette);
224 }
225 QScrollBar::changeEvent(e);
226 }
227
updatePalette(const QPalette & pal)228 void TerminalScrollBar::updatePalette(const QPalette &pal)
229 {
230 _backgroundMatchingPalette = pal;
231
232 auto proxyStyle = qobject_cast<const QProxyStyle *>(style());
233 const QStyle *appStyle = proxyStyle ? proxyStyle->baseStyle() : style();
234
235 // Scrollbars in widget styles like Fusion or Plastique do not work well with custom
236 // scrollbar coloring, in particular in conjunction with light terminal background colors.
237 // Use custom colors only for widget styles matched by the allowlist below, otherwise
238 // fall back to generic widget colors.
239 if (appStyle->objectName() == QLatin1String("breeze")) {
240 setPalette(_backgroundMatchingPalette);
241 } else {
242 setPalette(QGuiApplication::palette());
243 }
244 }
245
246 } // namespace Konsole
247