File indexing completed on 2024-05-19 05:28:18

0001 /*
0002     SPDX-FileCopyrightText: 2007 Robert Knight <robertknight@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006     This program is distributed in the hope that it will be useful,
0007     but WITHOUT ANY WARRANTY; without even the implied warranty of
0008     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0009     GNU General Public License for more details.
0010 
0011     You should have received a copy of the GNU General Public License
0012     along with this program; if not, write to the Free Software
0013     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0014     02110-1301  USA.
0015 */
0016 
0017 // Own
0018 #include "ScreenWindow.h"
0019 
0020 // Qt
0021 #include <QtDebug>
0022 
0023 // Konsole
0024 #include "Screen.h"
0025 
0026 using namespace Konsole;
0027 
0028 ScreenWindow::ScreenWindow(QObject *parent)
0029     : QObject(parent)
0030     , _screen(nullptr)
0031     , _windowBufferSize(0)
0032     , _bufferNeedsUpdate(true)
0033     , _windowLines(1)
0034     , _currentLine(0)
0035     , _trackOutput(true)
0036     , _scrollCount(0)
0037 {
0038 }
0039 ScreenWindow::~ScreenWindow() = default;
0040 
0041 void ScreenWindow::setScreen(Screen *screen)
0042 {
0043     Q_ASSERT(screen);
0044 
0045     _screen = screen;
0046 }
0047 
0048 Screen *ScreenWindow::screen() const
0049 {
0050     return _screen;
0051 }
0052 
0053 std::span<Character> ScreenWindow::getImage()
0054 {
0055     // reallocate internal buffer if the window size has changed
0056     int size = windowLines() * windowColumns();
0057     if (_windowBuffer.empty() || _windowBufferSize != size) {
0058         _windowBufferSize = size;
0059         _windowBuffer = std::vector<Character>(size);
0060         _bufferNeedsUpdate = true;
0061     }
0062 
0063     if (!_bufferNeedsUpdate)
0064         return _windowBuffer;
0065 
0066     _screen->getImage(_windowBuffer, size, currentLine(), endWindowLine());
0067 
0068     // this window may look beyond the end of the screen, in which
0069     // case there will be an unused area which needs to be filled
0070     // with blank characters
0071     fillUnusedArea();
0072 
0073     _bufferNeedsUpdate = false;
0074     return _windowBuffer;
0075 }
0076 
0077 void ScreenWindow::fillUnusedArea()
0078 {
0079     int screenEndLine = _screen->getHistLines() + _screen->getLines() - 1;
0080     int windowEndLine = currentLine() + windowLines() - 1;
0081 
0082     int unusedLines = windowEndLine - screenEndLine;
0083 
0084     // stop when unusedLines is negative; there is an issue w/ charsToFill
0085     //  being greater than an int can hold
0086     if (unusedLines <= 0) {
0087         return;
0088     }
0089 
0090     int charsToFill = unusedLines * windowColumns();
0091 
0092     Screen::fillWithDefaultChar(std::span(_windowBuffer).subspan(_windowBufferSize - charsToFill), charsToFill);
0093 }
0094 
0095 // return the index of the line at the end of this window, or if this window
0096 // goes beyond the end of the screen, the index of the line at the end
0097 // of the screen.
0098 //
0099 // when passing a line number to a Screen method, the line number should
0100 // never be more than endWindowLine()
0101 //
0102 int ScreenWindow::endWindowLine() const
0103 {
0104     return qMin(currentLine() + windowLines() - 1, lineCount() - 1);
0105 }
0106 QVector<LineProperty> ScreenWindow::getLineProperties()
0107 {
0108     QVector<LineProperty> result = _screen->getLineProperties(currentLine(), endWindowLine());
0109 
0110     if (result.size() != windowLines())
0111         result.resize(windowLines());
0112 
0113     return result;
0114 }
0115 
0116 QString ScreenWindow::selectedText(bool preserveLineBreaks) const
0117 {
0118     return _screen->selectedText(preserveLineBreaks);
0119 }
0120 
0121 void ScreenWindow::getSelectionStart(int &column, int &line)
0122 {
0123     _screen->getSelectionStart(column, line);
0124     line -= currentLine();
0125 }
0126 void ScreenWindow::getSelectionEnd(int &column, int &line)
0127 {
0128     _screen->getSelectionEnd(column, line);
0129     line -= currentLine();
0130 }
0131 void ScreenWindow::setSelectionStart(int column, int line, bool columnMode)
0132 {
0133     _screen->setSelectionStart(column, qMin(line + currentLine(), endWindowLine()), columnMode);
0134 
0135     _bufferNeedsUpdate = true;
0136     Q_EMIT selectionChanged();
0137 }
0138 
0139 void ScreenWindow::setSelectionEnd(int column, int line)
0140 {
0141     _screen->setSelectionEnd(column, qMin(line + currentLine(), endWindowLine()));
0142 
0143     _bufferNeedsUpdate = true;
0144     Q_EMIT selectionChanged();
0145 }
0146 
0147 bool ScreenWindow::isSelected(int column, int line)
0148 {
0149     return _screen->isSelected(column, qMin(line + currentLine(), endWindowLine()));
0150 }
0151 
0152 void ScreenWindow::clearSelection()
0153 {
0154     _screen->clearSelection();
0155 
0156     Q_EMIT selectionChanged();
0157 }
0158 
0159 void ScreenWindow::setWindowLines(int lines)
0160 {
0161     Q_ASSERT(lines > 0);
0162     _windowLines = lines;
0163 }
0164 int ScreenWindow::windowLines() const
0165 {
0166     return _windowLines;
0167 }
0168 
0169 int ScreenWindow::windowColumns() const
0170 {
0171     return _screen->getColumns();
0172 }
0173 
0174 int ScreenWindow::lineCount() const
0175 {
0176     return _screen->getHistLines() + _screen->getLines();
0177 }
0178 
0179 int ScreenWindow::columnCount() const
0180 {
0181     return _screen->getColumns();
0182 }
0183 
0184 QPoint ScreenWindow::cursorPosition() const
0185 {
0186     QPoint position;
0187 
0188     position.setX(_screen->getCursorX());
0189     position.setY(_screen->getCursorY());
0190 
0191     return position;
0192 }
0193 
0194 int ScreenWindow::currentLine() const
0195 {
0196     return qBound(0, _currentLine, lineCount() - windowLines());
0197 }
0198 
0199 void ScreenWindow::scrollBy(RelativeScrollMode mode, int amount)
0200 {
0201     if (mode == ScrollLines) {
0202         scrollTo(currentLine() + amount);
0203     } else if (mode == ScrollPages) {
0204         scrollTo(currentLine() + amount * (windowLines() / 2));
0205     }
0206 }
0207 
0208 bool ScreenWindow::atEndOfOutput() const
0209 {
0210     return currentLine() == (lineCount() - windowLines());
0211 }
0212 
0213 void ScreenWindow::scrollTo(int line)
0214 {
0215     int maxCurrentLineNumber = lineCount() - windowLines();
0216     line = qBound(0, line, maxCurrentLineNumber);
0217 
0218     const int delta = line - _currentLine;
0219     _currentLine = line;
0220 
0221     // keep track of number of lines scrolled by,
0222     // this can be reset by calling resetScrollCount()
0223     _scrollCount += delta;
0224 
0225     _bufferNeedsUpdate = true;
0226 
0227     Q_EMIT scrolled(_currentLine);
0228 }
0229 
0230 void ScreenWindow::setTrackOutput(bool trackOutput)
0231 {
0232     _trackOutput = trackOutput;
0233 }
0234 
0235 bool ScreenWindow::trackOutput() const
0236 {
0237     return _trackOutput;
0238 }
0239 
0240 int ScreenWindow::scrollCount() const
0241 {
0242     return _scrollCount;
0243 }
0244 
0245 void ScreenWindow::resetScrollCount()
0246 {
0247     _scrollCount = 0;
0248 }
0249 
0250 QRect ScreenWindow::scrollRegion() const
0251 {
0252     bool equalToScreenSize = windowLines() == _screen->getLines();
0253 
0254     if (atEndOfOutput() && equalToScreenSize)
0255         return _screen->lastScrolledRegion();
0256     else
0257         return {0, 0, windowColumns(), windowLines()};
0258 }
0259 
0260 void ScreenWindow::notifyOutputChanged()
0261 {
0262     // move window to the bottom of the screen and update scroll count
0263     // if this window is currently tracking the bottom of the screen
0264     if (_trackOutput) {
0265         _scrollCount -= _screen->scrolledLines();
0266         _currentLine = qMax(0, _screen->getHistLines() - (windowLines() - _screen->getLines()));
0267     } else {
0268         // if the history is not unlimited then it may
0269         // have run out of space and dropped the oldest
0270         // lines of output - in this case the screen
0271         // window's current line number will need to
0272         // be adjusted - otherwise the output will scroll
0273         _currentLine = qMax(0, _currentLine - _screen->droppedLines());
0274 
0275         // ensure that the screen window's current position does
0276         // not go beyond the bottom of the screen
0277         _currentLine = qMin(_currentLine, _screen->getHistLines());
0278     }
0279 
0280     _bufferNeedsUpdate = true;
0281 
0282     Q_EMIT outputChanged();
0283 }
0284 
0285 void ScreenWindow::handleCommandFromKeyboard(KeyboardTranslator::Command command)
0286 {
0287     // Keyboard-based navigation
0288     bool update = false;
0289 
0290     // EraseCommand is handled in Vt102Emulation
0291     if (command & KeyboardTranslator::ScrollPageUpCommand) {
0292         scrollBy(ScreenWindow::ScrollPages, -1);
0293         update = true;
0294     }
0295     if (command & KeyboardTranslator::ScrollPageDownCommand) {
0296         scrollBy(ScreenWindow::ScrollPages, 1);
0297         update = true;
0298     }
0299     if (command & KeyboardTranslator::ScrollLineUpCommand) {
0300         scrollBy(ScreenWindow::ScrollLines, -1);
0301         update = true;
0302     }
0303     if (command & KeyboardTranslator::ScrollLineDownCommand) {
0304         scrollBy(ScreenWindow::ScrollLines, 1);
0305         update = true;
0306     }
0307     if (command & KeyboardTranslator::ScrollDownToBottomCommand) {
0308         Q_EMIT scrollToEnd();
0309         update = true;
0310     }
0311     if (command & KeyboardTranslator::ScrollUpToTopCommand) {
0312         scrollTo(0);
0313         update = true;
0314     }
0315     // TODO: KeyboardTranslator::ScrollLockCommand
0316     // TODO: KeyboardTranslator::SendCommand
0317 
0318     if (update) {
0319         setTrackOutput(atEndOfOutput());
0320 
0321         Q_EMIT outputChanged();
0322     }
0323 }
0324 
0325 // #include "ScreenWindow.moc"