diff --git a/src/ScreenWindow.h b/src/ScreenWindow.h --- a/src/ScreenWindow.h +++ b/src/ScreenWindow.h @@ -53,6 +53,13 @@ Q_OBJECT public: + enum class SelectionMode { + Normal, + Word, + Column, + Line + }; + /** * Constructs a new screen window with the given parent. * A screen must be specified by calling setScreen() before calling getImage() or getLineProperties(). @@ -231,6 +238,52 @@ */ QString selectedText(const Screen::DecodingOptions options) const; + + /** + * Moving left/up from the line containing pnt, return the starting + * offset point which the given line is continuously wrapped + * (top left corner = 0,0; previous line not visible = 0,-1). + */ + QPoint findLineStart(const QPoint &pnt); + + /** + * Moving right/down from the line containing pnt, return the ending + * offset point which the given line is continuously wrapped. + */ + QPoint findLineEnd(const QPoint &pnt); + + QPoint findWordStart(const QPoint &pnt); + QPoint findWordEnd(const QPoint &pnt); + + int selectionState() const { return _actSel; } + void setSelectionState(const int state) { _actSel = state; } + + // classifies the 'ch' into one of three categories + // and returns a character to indicate which category it is in + // + // - A space (returns ' ') + // - Part of a word (returns 'a') + // - Other characters (returns the input character) + QChar charClass(const Character &ch) const; + + /** + * Sets which characters, in addition to letters and numbers, + * are regarded as being part of a word for the purposes + * of selecting words in the display by double clicking on them. + * + * The word boundaries occur at the first and last characters which + * are either a letter, number, or a character in @p wc + * + * @param wc An array of characters which are to be considered parts + * of a word ( in addition to letters and numbers ). + */ + void setWordCharacters(const QString &wc); + + void extendSelection(const QPoint &position, const SelectionMode selectionMode); + void selectLine(QPoint pos, bool entireLine); + void selectWord(QPoint pos); + void startNormalSelection(QPoint pos); + public Q_SLOTS: /** * Notifies the window that the contents of the associated terminal screen have changed. @@ -262,6 +315,7 @@ int endWindowLine() const; void fillUnusedArea(); + int loc(int x, int y) const; Screen *_screen; // see setScreen() , screen() Character *_windowBuffer; @@ -273,6 +327,12 @@ int _currentResultLine; bool _trackOutput; // see setTrackOutput() , trackOutput() int _scrollCount; // count of lines which the window has been scrolled by since + QString _wordCharacters; + QPoint _initialSelectionPoint; // initial selection point + QPoint _initialSelectionEnd; + QPoint _currentSelectionPoint; // current selection point + QPoint _tripleSelBegin; // help avoid flicker + int _actSel = 0; // selection state // the last call to resetScrollCount() }; } diff --git a/src/ScreenWindow.cpp b/src/ScreenWindow.cpp --- a/src/ScreenWindow.cpp +++ b/src/ScreenWindow.cpp @@ -22,6 +22,10 @@ // Konsole #include "Screen.h" +#include "ExtendedCharTable.h" +#include + +#include using namespace Konsole; @@ -102,6 +106,16 @@ Screen::fillWithDefaultChar(_windowBuffer + _windowBufferSize - charsToFill, charsToFill); } +int ScreenWindow::loc(int x, int y) const +{ + Q_ASSERT(y >= 0 && y < lineCount()); + Q_ASSERT(x >= 0 && x < columnCount()); + x = qBound(0, x, columnCount() - 1); + y = qBound(0, y, lineCount() - 1); + + return y * columnCount() + x; +} + // return the index of the line at the end of this window, or if this window // goes beyond the end of the screen, the index of the line at the end // of the screen. @@ -131,6 +145,436 @@ return _screen->selectedText(options); } +QPoint ScreenWindow::findLineStart(const QPoint &pnt) +{ + QVector lineProperties = getLineProperties(); + + const int visibleScreenLines = lineProperties.size(); + const int topVisibleLine = currentLine(); + int line = pnt.y(); + int lineInHistory= line + topVisibleLine; + + + while (lineInHistory > 0) { + for (; line > 0; line--, lineInHistory--) { + // Does previous line wrap around? + if ((lineProperties[line - 1] & LINE_WRAPPED) == 0) { + return {0, lineInHistory - topVisibleLine}; + } + } + + if (lineInHistory < 1) { + break; + } + + // _lineProperties is only for the visible screen, so grab new data + int newRegionStart = qMax(0, lineInHistory - visibleScreenLines); + lineProperties = _screen->getLineProperties(newRegionStart, lineInHistory - 1); + line = lineInHistory - newRegionStart; + } + + return {0, lineInHistory - topVisibleLine}; +} + +QPoint ScreenWindow::findLineEnd(const QPoint &pnt) +{ + QVector lineProperties = getLineProperties(); + + const int visibleScreenLines = lineProperties.size(); + const int topVisibleLine = currentLine(); + const int maxY = lineCount() - 1; + int line = pnt.y(); + int lineInHistory= line + topVisibleLine; + + while (lineInHistory < maxY) { + for (; line < lineProperties.count() && lineInHistory < maxY; line++, lineInHistory++) { + // Does current line wrap around? + if ((lineProperties[line] & LINE_WRAPPED) == 0) { + return {columnCount() - 1, lineInHistory - topVisibleLine}; + } + } + + line = 0; + lineProperties = _screen->getLineProperties(lineInHistory, qMin(lineInHistory + visibleScreenLines, maxY)); + } + return {columnCount() - 1, lineInHistory - topVisibleLine}; +} + +QPoint ScreenWindow::findWordStart(const QPoint &pnt) +{ + const int regSize = qMax(windowLines(), 10); + const int firstVisibleLine = currentLine(); + + Character *image = getImage(); + std::unique_ptr tempImage; + + int imgLine = pnt.y(); + + if (imgLine < 0 || imgLine >= lineCount()) { + return pnt; + } + + int x = pnt.x(); + int y = imgLine + firstVisibleLine; + int imgLoc = loc(x, imgLine); + QVector lineProperties = getLineProperties(); + const QChar selClass = charClass(image[imgLoc]); + const int imageSize = regSize * columnCount(); + + while (imgLoc >= 0) { + for (; imgLoc > 0 && imgLine >= 0; imgLoc--, x--) { + const QChar &curClass = charClass(image[imgLoc - 1]); + if (curClass != selClass) { + return {x, y - firstVisibleLine}; + } + + // has previous char on this line + if (x > 0) { + continue; + } + + // not the first line in the session + if ((lineProperties[imgLine - 1] & LINE_WRAPPED) == 0) { + return {x, y - firstVisibleLine}; + } + + // have continuation on prev line + x = columnCount(); + imgLine--; + y--; + } + + if (y <= 0) { + return {x, y - firstVisibleLine}; + } + + // Fetch new region + const int newRegStart = qMax(y - regSize + 1, 0); + lineProperties = _screen->getLineProperties(newRegStart, y - 1); + imgLine = y - newRegStart; + + if (!tempImage) { + tempImage.reset(new Character[imageSize]); + image = tempImage.get(); + } + + _screen->getImage(image, imageSize, newRegStart, y - 1); + imgLoc = loc(x, imgLine); + } + + return {x, y - firstVisibleLine}; + +} + +QPoint ScreenWindow::findWordEnd(const QPoint &pnt) +{ + const int regSize = qMax(windowLines(), 10); + const int curLine = currentLine(); + int line = pnt.y(); + + // The selection is already scrolled out of view, so assume it is already at a boundary + if (line < 0 || line >= lineCount()) { + return pnt; + } + + int x = pnt.x(); + int y = line + curLine; + QVector lineProperties = getLineProperties(); + Character *image = getImage(); + std::unique_ptr tempImage; + + int imgPos = loc(x, line); + const QChar selClass = charClass(image[imgPos]); + QChar curClass; + QChar nextClass; + + const int imageSize = regSize * columnCount(); + const int maxY = lineCount(); + const int maxX = columnCount() - 1; + + while (x >= 0 && line >= 0) { + imgPos = loc(x, line); + + const int visibleLinesCount = lineProperties.count(); + bool changedClass = false; + + for (;y < maxY && line < visibleLinesCount; imgPos++, x++) { + curClass = charClass(image[imgPos + 1]); + nextClass = charClass(image[imgPos + 2]); + + changedClass = curClass != selClass && + // A colon right before whitespace is never part of a word + !(image[imgPos + 1].character == ':' && nextClass == QLatin1Char(' ')); + + if (changedClass) { + break; + } + + if (x >= maxX) { + if ((lineProperties[line] & LINE_WRAPPED) == 0) { + break; + } + + line++; + y++; + x = -1; + } + } + + if (changedClass) { + break; + } + + if (line < visibleLinesCount && ((lineProperties[line] & LINE_WRAPPED) == 0)) { + break; + } + + const int newRegEnd = qMin(y + regSize - 1, maxY - 1); + lineProperties = _screen->getLineProperties(y, newRegEnd); + if (!tempImage) { + tempImage.reset(new Character[imageSize]); + image = tempImage.get(); + } + _screen->getImage(tempImage.get(), imageSize, y, newRegEnd); + + line = 0; + } + + y -= curLine; + // In word selection mode don't select @ (64) if at end of word. + if (((image[imgPos].rendition & RE_EXTENDED_CHAR) == 0) && + (QChar(image[imgPos].character) == QLatin1Char('@')) && + (y > pnt.y() || x > pnt.x())) { + if (x > 0) { + x--; + } else { + y--; + } + } + + return {x, y}; + +} + +QChar ScreenWindow::charClass(const Character &ch) const +{ + if ((ch.rendition & RE_EXTENDED_CHAR) != 0) { + ushort extendedCharLength = 0; + const uint* chars = ExtendedCharTable::instance.lookupExtendedChar(ch.character, extendedCharLength); + if ((chars != nullptr) && extendedCharLength > 0) { + const QString s = QString::fromUcs4(chars, extendedCharLength); + if (_wordCharacters.contains(s, Qt::CaseInsensitive)) { + return QLatin1Char('a'); + } + bool letterOrNumber = false; + for (int i = 0; !letterOrNumber && i < s.size(); ++i) { + letterOrNumber = s.at(i).isLetterOrNumber(); + } + return letterOrNumber ? QLatin1Char('a') : s.at(0); + } + return 0; + } else { + const QChar qch(ch.character); + if (qch.isSpace()) { + return QLatin1Char(' '); + } + + if (qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive)) { + return QLatin1Char('a'); + } + + return qch; + } +} + +void ScreenWindow::setWordCharacters(const QString &wc) +{ + _wordCharacters = wc; +} + +void ScreenWindow::extendSelection(const QPoint &rawPosition, const ScreenWindow::SelectionMode selectionMode) +{ + QPoint cursorPosition = rawPosition; + + const QRect characterBounds(0, 0, windowColumns(), windowLines()); + + int linesBeyondWidget = 0; + if (!characterBounds.contains(cursorPosition)) { + cursorPosition.setX(qBound(0, cursorPosition.x(), windowColumns() - 1)); + cursorPosition.setY(qBound(0, cursorPosition.y(), windowLines() - 1)); + + linesBeyondWidget = rawPosition.y() - cursorPosition.y(); + if (linesBeyondWidget < 0) { + scrollTo(currentLine() + linesBeyondWidget); + } else if (linesBeyondWidget > 0) { + scrollTo(currentLine() + linesBeyondWidget); + } + } + + QPoint selectionEnd = cursorPosition; + QPoint selectionStart; + QPoint initialSelectionBegin = _initialSelectionPoint; + initialSelectionBegin.ry() -= currentLine(); + QPoint initialSelectionEnd = _initialSelectionEnd; + initialSelectionEnd.ry() -= currentLine(); + QPoint previousSelectionPoint = _currentSelectionPoint; + previousSelectionPoint.ry() -= currentLine(); + bool swapping = false; + + int offset = 0; + + if (selectionMode == SelectionMode::Word) { + // Extend to word boundaries + const bool extendingLeft = (selectionEnd.y() < initialSelectionBegin.y() || + (selectionEnd.y() == initialSelectionBegin.y() && selectionEnd.x() < initialSelectionBegin.x())); + const bool wasExtendingLeft = (previousSelectionPoint.y() < initialSelectionBegin.y() || + (previousSelectionPoint.y() == initialSelectionBegin.y() && previousSelectionPoint.x() < initialSelectionBegin.x())); + swapping = extendingLeft != wasExtendingLeft; + + if (extendingLeft) { + selectionEnd = findWordStart(cursorPosition); + selectionStart = initialSelectionEnd; + } else { + selectionEnd = findWordEnd(cursorPosition); + selectionStart = initialSelectionBegin; + } + + selectionStart.rx()++; + } + + if (selectionMode == SelectionMode::Line) { + // Extend to complete line + const bool extendingUpwards = (selectionEnd.y() < initialSelectionBegin.y()); + const bool wasExtendingUpwards = (previousSelectionPoint.y() < initialSelectionBegin.y()); + swapping = extendingUpwards != wasExtendingUpwards; + if (extendingUpwards) { + selectionEnd = findLineStart(selectionEnd); + selectionStart = initialSelectionEnd; + } else { + selectionStart = initialSelectionBegin; + selectionEnd = findLineEnd(selectionEnd); + } + + _tripleSelBegin = selectionStart; + + selectionStart.rx()++; + } + + if (selectionMode == SelectionMode::Normal) { + QChar selClass; + + const bool extendingLeft = (selectionEnd.y() < initialSelectionBegin.y() || + (selectionEnd.y() == initialSelectionBegin.y() && selectionEnd.x() < initialSelectionBegin.x())); + const bool wasExtendingLeft = (previousSelectionPoint.y() < initialSelectionBegin.y() || + (previousSelectionPoint.y() == initialSelectionBegin.y() && previousSelectionPoint.x() < initialSelectionBegin.x())); + swapping = extendingLeft != wasExtendingLeft; + + // Find left (left_not_right ? from here : from start) + const QPoint left = extendingLeft ? selectionEnd : initialSelectionBegin; + + Character *image = getImage(); + // Find left (left_not_right ? from start : from here) + QPoint right = extendingLeft ? initialSelectionBegin : selectionEnd; + if (right.x() > 0 && selectionMode != SelectionMode::Column) { + if (right.x() - 1 < columnCount() && right.y() < windowLines()) { + selClass = charClass(image[loc(right.x() - 1, right.y())]); + } + } + + // Pick which is start (ohere) and which is extension (here) + if (extendingLeft) { + selectionEnd = left; + selectionStart = right; + offset = 0; + } else { + selectionEnd = right; + selectionStart = left; + offset = -1; + } + } + + if ((selectionEnd == previousSelectionPoint) && linesBeyondWidget == 0) { + return; // not moved + } + + if (selectionEnd == selectionStart) { + return; // It's not left, it's not right. + } + + if (_actSel < 2 || swapping) { + if (selectionMode == SelectionMode::Column) { + setSelectionStart(selectionStart.x() , selectionStart.y() , true); + } else { + setSelectionStart(selectionStart.x() - 1 - offset , selectionStart.y() , false); + } + } + + _actSel = 2; // within selection + _currentSelectionPoint = selectionEnd; + _currentSelectionPoint.ry() += currentLine(); + + if (selectionMode == SelectionMode::Column) { + setSelectionEnd(selectionEnd.x() , selectionEnd.y()); + } else { + setSelectionEnd(selectionEnd.x() + offset , selectionEnd.y()); + } + +} + +void ScreenWindow::selectLine(QPoint pos, bool entireLine) +{ + _initialSelectionPoint = pos; + + clearSelection(); + + _actSel = 2; // within selection + + if (!entireLine) { // Select from cursor to end of line + _tripleSelBegin = findWordStart(pos); + setSelectionStart(_tripleSelBegin.x(), + _tripleSelBegin.y() , false); + } else { + _tripleSelBegin = findLineStart(pos); + setSelectionStart(0 , _tripleSelBegin.y() , false); + } + + _initialSelectionPoint = findLineStart(_initialSelectionPoint); + _initialSelectionEnd = findLineEnd(pos); + + setSelectionEnd(_initialSelectionEnd.x() , _initialSelectionEnd.y()); + + _initialSelectionPoint.ry() += currentLine(); + _initialSelectionEnd.ry() += currentLine(); + +} + +void ScreenWindow::selectWord(QPoint pos) +{ + clearSelection(); + + _currentSelectionPoint = pos; + _currentSelectionPoint.ry() += currentLine(); + + _actSel = 2; // within selection + + // find word boundaries... + // find the start of the word + const QPoint bgnSel = findWordStart(pos); + const QPoint endSel = findWordEnd(pos); + + _actSel = 2; // within selection + + setSelectionStart(bgnSel.x() , bgnSel.y() , false); + setSelectionEnd(endSel.x() , endSel.y()); + + _initialSelectionPoint = bgnSel; + _initialSelectionPoint.ry() += currentLine(); + + _initialSelectionEnd = endSel; + _initialSelectionEnd.ry() += currentLine(); + +} + void ScreenWindow::getSelectionStart(int &column, int &line) { _screen->getSelectionStart(column, line); @@ -329,3 +773,10 @@ emit outputChanged(); } + +void ScreenWindow::startNormalSelection(QPoint pos) +{ + clearSelection(); + _initialSelectionPoint = _currentSelectionPoint = pos; + _actSel = 1; // left mouse button pressed but nothing selected yet. +} diff --git a/src/TerminalDisplay.h b/src/TerminalDisplay.h --- a/src/TerminalDisplay.h +++ b/src/TerminalDisplay.h @@ -354,16 +354,6 @@ * of a word ( in addition to letters and numbers ). */ void setWordCharacters(const QString &wc); - /** - * Returns the characters which are considered part of a word for the - * purpose of selecting words in the display with the mouse. - * - * @see setWordCharacters() - */ - QString wordCharacters() const - { - return _wordCharacters; - } /** * Sets the type of effect used to alert the user when a 'bell' occurs in the @@ -779,18 +769,9 @@ QDrag *dragObject; } _dragInfo; - // classifies the 'ch' into one of three categories - // and returns a character to indicate which category it is in - // - // - A space (returns ' ') - // - Part of a word (returns 'a') - // - Other characters (returns the input character) - QChar charClass(const Character &ch) const; - void clearImage(); void mouseTripleClickEvent(QMouseEvent *ev); - void selectLine(QPoint pos, bool entireLine); // reimplemented void inputMethodEvent(QInputMethodEvent *event) Q_DECL_OVERRIDE; @@ -908,11 +889,6 @@ void processMidButtonClick(QMouseEvent *ev); - QPoint findLineStart(const QPoint &pnt); - QPoint findLineEnd(const QPoint &pnt); - QPoint findWordStart(const QPoint &pnt); - QPoint findWordEnd(const QPoint &pnt); - // Uses the current settings for trimming whitespace and preserving linebreaks to create a proper flag value for Screen Screen::DecodingOptions currentDecodingOptions(); @@ -964,11 +940,6 @@ bool _alternateScrolling; bool _bracketedPasteMode; - QPoint _initialSelectionPoint; // initial selection point - QPoint _initialSelectionEnd; - QPoint _currentSelectionPoint; // current selection point - QPoint _tripleSelBegin; // help avoid flicker - int _actSel; // selection state bool _wordSelectionMode; bool _lineSelectionMode; bool _preserveLineBreaks; diff --git a/src/TerminalDisplay.cpp b/src/TerminalDisplay.cpp --- a/src/TerminalDisplay.cpp +++ b/src/TerminalDisplay.cpp @@ -45,6 +45,7 @@ #include #include #include +#include // KDE #include @@ -131,6 +132,7 @@ _filterUpdateRequired = true; }); _screenWindow->setWindowLines(_lines); + _screenWindow->setWordCharacters(_wordCharacters); } } @@ -419,10 +421,6 @@ , _usesMouseTracking(false) , _alternateScrolling(true) , _bracketedPasteMode(false) - , _initialSelectionPoint(QPoint()) - , _currentSelectionPoint(QPoint()) - , _tripleSelBegin(QPoint()) - , _actSel(0) , _wordSelectionMode(false) , _lineSelectionMode(false) , _preserveLineBreaks(true) @@ -2478,8 +2476,7 @@ _screenWindow->clearSelection(); pos.ry() += _scrollBar->value(); - _initialSelectionPoint = _currentSelectionPoint = pos; - _actSel = 1; // left mouse button pressed but nothing selected yet. + _screenWindow->startNormalSelection(pos); } else if (_usesMouseTracking && !_readOnly) { emit mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0); } @@ -2618,7 +2615,7 @@ return; } - if (_actSel == 0) { + if (_screenWindow->selectionState() == 0) { return; } @@ -2645,153 +2642,25 @@ return; } - //if ( !contentsRect().contains(ev->pos()) ) return; - const QPoint tL = contentsRect().topLeft(); - const int tLx = tL.x(); - const int tLy = tL.y(); - const int scroll = _scrollBar->value(); - // we're in the process of moving the mouse with the left button pressed // the mouse cursor will kept caught within the bounds of the text in // this widget. - int linesBeyondWidget = 0; - - const QRect textBounds(tLx + _contentRect.left(), - tLy + _contentRect.top(), - _usedColumns * _fontWidth - 1, - _usedLines * _fontHeight - 1); - - const QRect characterBounds(0, 0, _columns, _lines); - - QPoint pos = position; - - // Adjust position within text area bounds. - const QPoint oldpos = pos; - - pos.setX(qBound(textBounds.left(), pos.x(), textBounds.right())); - pos.setY(qBound(textBounds.top(), pos.y(), textBounds.bottom())); - - if (oldpos.y() > textBounds.bottom()) { - linesBeyondWidget = (oldpos.y() - textBounds.bottom()) / _fontHeight; - _scrollBar->setValue(_scrollBar->value() + linesBeyondWidget + 1); // scrollforward - } - if (oldpos.y() < textBounds.top()) { - linesBeyondWidget = (textBounds.top() - oldpos.y()) / _fontHeight; - _scrollBar->setValue(_scrollBar->value() - linesBeyondWidget - 1); // history - } - - int charColumn = 0; - int charLine = 0; - getCharacterPosition(pos, charLine, charColumn, !_wordSelectionMode && !_lineSelectionMode); - - const QPoint cursorPosition(charColumn, charLine); - QPoint selectionEnd = QPoint(charColumn, charLine); - QPoint selectionStart; - QPoint initialSelectionBegin = _initialSelectionPoint; - initialSelectionBegin.ry() -= _scrollBar->value(); - QPoint initialSelectionEnd = _initialSelectionEnd; - initialSelectionEnd.ry() -= _scrollBar->value(); - QPoint previousSelectionPoint = _currentSelectionPoint; - previousSelectionPoint.ry() -= _scrollBar->value(); - bool swapping = false; - - int offset = 0; - - if (_wordSelectionMode) { - // Extend to word boundaries - const bool extendingLeft = (selectionEnd.y() < initialSelectionBegin.y() || - (selectionEnd.y() == initialSelectionBegin.y() && selectionEnd.x() < initialSelectionBegin.x())); - const bool wasExtendingLeft = (previousSelectionPoint.y() < initialSelectionBegin.y() || - (previousSelectionPoint.y() == initialSelectionBegin.y() && previousSelectionPoint.x() < initialSelectionBegin.x())); - swapping = extendingLeft != wasExtendingLeft; - - if (extendingLeft) { - selectionEnd = findWordStart(cursorPosition); - selectionStart = initialSelectionEnd; - } else { - selectionEnd = findWordEnd(cursorPosition); - selectionStart = initialSelectionBegin; - } - - selectionStart.rx()++; - } + int column = qRound((float(position.x() - contentsRect().left() - _contentRect.left()) / _fontWidth)); + int line = qFloor(float(position.y() - contentsRect().top() - _contentRect.top()) / _fontHeight); + const QPoint cursorPosition(column, line); if (_lineSelectionMode) { - // Extend to complete line - const bool extendingUpwards = (selectionEnd.y() < initialSelectionBegin.y()); - const bool wasExtendingUpwards = (previousSelectionPoint.y() < initialSelectionBegin.y()); - swapping = extendingUpwards != wasExtendingUpwards; - if (extendingUpwards) { - selectionEnd = findLineStart(selectionEnd); - selectionStart = initialSelectionEnd; - } else { - selectionStart = initialSelectionBegin; - selectionEnd = findLineEnd(selectionEnd); - } - - _tripleSelBegin = selectionStart; - - selectionStart.rx()++; - } - - if (!_wordSelectionMode && !_lineSelectionMode) { - QChar selClass; - - const bool extendingLeft = (selectionEnd.y() < initialSelectionBegin.y() || - (selectionEnd.y() == initialSelectionBegin.y() && selectionEnd.x() < initialSelectionBegin.x())); - const bool wasExtendingLeft = (previousSelectionPoint.y() < initialSelectionBegin.y() || - (previousSelectionPoint.y() == initialSelectionBegin.y() && previousSelectionPoint.x() < initialSelectionBegin.x())); - swapping = extendingLeft != wasExtendingLeft; - - // Find left (left_not_right ? from here : from start) - const QPoint left = extendingLeft ? selectionEnd : initialSelectionBegin; - - // Find left (left_not_right ? from start : from here) - QPoint right = extendingLeft ? initialSelectionBegin : selectionEnd; - if (right.x() > 0 && !_columnSelectionMode) { - if (right.x() - 1 < _columns && right.y() < _lines) { - selClass = charClass(_image[loc(right.x() - 1, right.y())]); - } - } - - // Pick which is start (ohere) and which is extension (here) - if (extendingLeft) { - selectionEnd = left; - selectionStart = right; - offset = 0; - } else { - selectionEnd = right; - selectionStart = left; - offset = -1; - } - } - - if ((selectionEnd == previousSelectionPoint) && (scroll == _scrollBar->value())) { - return; // not moved - } - - if (selectionEnd == selectionStart) { - return; // It's not left, it's not right. - } - - if (_actSel < 2 || swapping) { - if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) { - _screenWindow->setSelectionStart(selectionStart.x() , selectionStart.y() , true); - } else { - _screenWindow->setSelectionStart(selectionStart.x() - 1 - offset , selectionStart.y() , false); - } - } - - _actSel = 2; // within selection - _currentSelectionPoint = selectionEnd; - _currentSelectionPoint.ry() += _scrollBar->value(); - - if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) { - _screenWindow->setSelectionEnd(selectionEnd.x() , selectionEnd.y()); + _screenWindow->extendSelection(cursorPosition, ScreenWindow::SelectionMode::Line); + } else if (_wordSelectionMode) { + _screenWindow->extendSelection(cursorPosition, ScreenWindow::SelectionMode::Word); + } else if (_columnSelectionMode) { + _screenWindow->extendSelection(cursorPosition, ScreenWindow::SelectionMode::Column); } else { - _screenWindow->setSelectionEnd(selectionEnd.x() + offset , selectionEnd.y()); + _screenWindow->extendSelection(cursorPosition, ScreenWindow::SelectionMode::Normal); } + + _scrollBar->setValue(_screenWindow->currentLine()); } void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev) @@ -2809,11 +2678,11 @@ // We had a drag event pending but never confirmed. Kill selection _screenWindow->clearSelection(); } else { - if (_actSel > 1) { + if (_screenWindow->selectionState() > 1) { copyToX11Selection(); } - _actSel = 0; + _screenWindow->setSelectionState(0); //FIXME: emits a release event even if the mouse is // outside the range. The procedure used in `mouseMoveEvent' @@ -2917,32 +2786,12 @@ return; } - _screenWindow->clearSelection(); - _currentSelectionPoint = pos; - _currentSelectionPoint.ry() += _scrollBar->value(); + _screenWindow->selectWord(pos); _wordSelectionMode = true; - _actSel = 2; // within selection + _lineSelectionMode = false; - // find word boundaries... - { - // find the start of the word - const QPoint bgnSel = findWordStart(pos); - const QPoint endSel = findWordEnd(pos); - - _actSel = 2; // within selection - - _screenWindow->setSelectionStart(bgnSel.x() , bgnSel.y() , false); - _screenWindow->setSelectionEnd(endSel.x() , endSel.y()); - - _initialSelectionPoint = bgnSel; - _initialSelectionPoint.ry() += _scrollBar->value(); - - _initialSelectionEnd = endSel; - _initialSelectionEnd.ry() += _scrollBar->value(); - - copyToX11Selection(); - } + copyToX11Selection(); _possibleTripleClick = true; @@ -3034,224 +2883,6 @@ _sessionController->setSearchStartToWindowCurrentLine(); } -/* Moving left/up from the line containing pnt, return the starting - offset point which the given line is continuously wrapped - (top left corner = 0,0; previous line not visible = 0,-1). -*/ -QPoint TerminalDisplay::findLineStart(const QPoint &pnt) -{ - const int visibleScreenLines = _lineProperties.size(); - const int topVisibleLine = _screenWindow->currentLine(); - Screen *screen = _screenWindow->screen(); - int line = pnt.y(); - int lineInHistory= line + topVisibleLine; - - QVector lineProperties = _lineProperties; - - while (lineInHistory > 0) { - for (; line > 0; line--, lineInHistory--) { - // Does previous line wrap around? - if ((lineProperties[line - 1] & LINE_WRAPPED) == 0) { - return {0, lineInHistory - topVisibleLine}; - } - } - - if (lineInHistory < 1) { - break; - } - - // _lineProperties is only for the visible screen, so grab new data - int newRegionStart = qMax(0, lineInHistory - visibleScreenLines); - lineProperties = screen->getLineProperties(newRegionStart, lineInHistory - 1); - line = lineInHistory - newRegionStart; - } - return {0, lineInHistory - topVisibleLine}; -} - -/* Moving right/down from the line containing pnt, return the ending - offset point which the given line is continuously wrapped. -*/ -QPoint TerminalDisplay::findLineEnd(const QPoint &pnt) -{ - const int visibleScreenLines = _lineProperties.size(); - const int topVisibleLine = _screenWindow->currentLine(); - const int maxY = _screenWindow->lineCount() - 1; - Screen *screen = _screenWindow->screen(); - int line = pnt.y(); - int lineInHistory= line + topVisibleLine; - - QVector lineProperties = _lineProperties; - - while (lineInHistory < maxY) { - for (; line < lineProperties.count() && lineInHistory < maxY; line++, lineInHistory++) { - // Does current line wrap around? - if ((lineProperties[line] & LINE_WRAPPED) == 0) { - return {_columns - 1, lineInHistory - topVisibleLine}; - } - } - - line = 0; - lineProperties = screen->getLineProperties(lineInHistory, qMin(lineInHistory + visibleScreenLines, maxY)); - } - return {_columns - 1, lineInHistory - topVisibleLine}; -} - -QPoint TerminalDisplay::findWordStart(const QPoint &pnt) -{ - const int regSize = qMax(_screenWindow->windowLines(), 10); - const int firstVisibleLine = _screenWindow->currentLine(); - - Screen *screen = _screenWindow->screen(); - Character *image = _image; - std::unique_ptr tempImage; - - int imgLine = pnt.y(); - - if (imgLine < 0 || imgLine >= _lines) { - return pnt; - } - - int x = pnt.x(); - int y = imgLine + firstVisibleLine; - int imgLoc = loc(x, imgLine); - QVector lineProperties = _lineProperties; - const QChar selClass = charClass(image[imgLoc]); - const int imageSize = regSize * _columns; - - while (imgLoc >= 0) { - for (; imgLoc > 0 && imgLine >= 0; imgLoc--, x--) { - const QChar &curClass = charClass(image[imgLoc - 1]); - if (curClass != selClass) { - return {x, y - firstVisibleLine}; - } - - // has previous char on this line - if (x > 0) { - continue; - } - - // not the first line in the session - if ((lineProperties[imgLine - 1] & LINE_WRAPPED) == 0) { - return {x, y - firstVisibleLine}; - } - - // have continuation on prev line - x = _columns; - imgLine--; - y--; - } - - if (y <= 0) { - return {x, y - firstVisibleLine}; - } - - // Fetch new region - const int newRegStart = qMax(y - regSize + 1, 0); - lineProperties = screen->getLineProperties(newRegStart, y - 1); - imgLine = y - newRegStart; - - if (!tempImage) { - tempImage.reset(new Character[imageSize]); - image = tempImage.get(); - } - - screen->getImage(image, imageSize, newRegStart, y - 1); - imgLoc = loc(x, imgLine); - } - - return {x, y - firstVisibleLine}; -} - -QPoint TerminalDisplay::findWordEnd(const QPoint &pnt) -{ - const int regSize = qMax(_screenWindow->windowLines(), 10); - const int curLine = _screenWindow->currentLine(); - int line = pnt.y(); - - // The selection is already scrolled out of view, so assume it is already at a boundary - if (line < 0 || line >= _lines) { - return pnt; - } - - int x = pnt.x(); - int y = line + curLine; - QVector lineProperties = _lineProperties; - Screen *screen = _screenWindow->screen(); - Character *image = _image; - std::unique_ptr tempImage; - - int imgPos = loc(x, line); - const QChar selClass = charClass(image[imgPos]); - QChar curClass; - QChar nextClass; - - const int imageSize = regSize * _columns; - const int maxY = _screenWindow->lineCount(); - const int maxX = _columns - 1; - - while (x >= 0 && line >= 0) { - imgPos = loc(x, line); - - const int visibleLinesCount = lineProperties.count(); - bool changedClass = false; - - for (;y < maxY && line < visibleLinesCount; imgPos++, x++) { - curClass = charClass(image[imgPos + 1]); - nextClass = charClass(image[imgPos + 2]); - - changedClass = curClass != selClass && - // A colon right before whitespace is never part of a word - !(image[imgPos + 1].character == ':' && nextClass == QLatin1Char(' ')); - - if (changedClass) { - break; - } - - if (x >= maxX) { - if ((lineProperties[line] & LINE_WRAPPED) == 0) { - break; - } - - line++; - y++; - x = -1; - } - } - - if (changedClass) { - break; - } - - if (line < visibleLinesCount && ((lineProperties[line] & LINE_WRAPPED) == 0)) { - break; - } - - const int newRegEnd = qMin(y + regSize - 1, maxY - 1); - lineProperties = screen->getLineProperties(y, newRegEnd); - if (!tempImage) { - tempImage.reset(new Character[imageSize]); - image = tempImage.get(); - } - screen->getImage(tempImage.get(), imageSize, y, newRegEnd); - - line = 0; - } - - y -= curLine; - // In word selection mode don't select @ (64) if at end of word. - if (((image[imgPos].rendition & RE_EXTENDED_CHAR) == 0) && - (QChar(image[imgPos].character) == QLatin1Char('@')) && - (y > pnt.y() || x > pnt.x())) { - if (x > 0) { - x--; - } else { - y--; - } - } - - return {x, y}; -} - Screen::DecodingOptions TerminalDisplay::currentDecodingOptions() { Screen::DecodingOptions decodingOptions; @@ -3277,39 +2908,13 @@ int charLine; int charColumn; getCharacterPosition(ev->pos(), charLine, charColumn, true); - selectLine(QPoint(charColumn, charLine), + _screenWindow->selectLine(QPoint(charColumn, charLine), _tripleClickMode == Enum::SelectWholeLine); -} - -void TerminalDisplay::selectLine(QPoint pos, bool entireLine) -{ - _initialSelectionPoint = pos; - - _screenWindow->clearSelection(); _lineSelectionMode = true; _wordSelectionMode = false; - _actSel = 2; // within selection - - if (!entireLine) { // Select from cursor to end of line - _tripleSelBegin = findWordStart(pos); - _screenWindow->setSelectionStart(_tripleSelBegin.x(), - _tripleSelBegin.y() , false); - } else { - _tripleSelBegin = findLineStart(pos); - _screenWindow->setSelectionStart(0 , _tripleSelBegin.y() , false); - } - - _initialSelectionPoint = findLineStart(_initialSelectionPoint); - _initialSelectionEnd = findLineEnd(pos); - - _screenWindow->setSelectionEnd(_initialSelectionEnd.x() , _initialSelectionEnd.y()); - copyToX11Selection(); - - _initialSelectionPoint.ry() += _scrollBar->value(); - _initialSelectionEnd.ry() += _scrollBar->value(); } void TerminalDisplay::selectCurrentLine() @@ -3318,7 +2923,12 @@ return; } - selectLine(cursorPosition(), true); + _screenWindow->selectLine(cursorPosition(), true); + + _lineSelectionMode = true; + _wordSelectionMode = false; + + copyToX11Selection(); } void TerminalDisplay::selectAll() @@ -3343,40 +2953,13 @@ } } -QChar TerminalDisplay::charClass(const Character& ch) const -{ - if ((ch.rendition & RE_EXTENDED_CHAR) != 0) { - ushort extendedCharLength = 0; - const uint* chars = ExtendedCharTable::instance.lookupExtendedChar(ch.character, extendedCharLength); - if ((chars != nullptr) && extendedCharLength > 0) { - const QString s = QString::fromUcs4(chars, extendedCharLength); - if (_wordCharacters.contains(s, Qt::CaseInsensitive)) { - return QLatin1Char('a'); - } - bool letterOrNumber = false; - for (int i = 0; !letterOrNumber && i < s.size(); ++i) { - letterOrNumber = s.at(i).isLetterOrNumber(); - } - return letterOrNumber ? QLatin1Char('a') : s.at(0); - } - return 0; - } else { - const QChar qch(ch.character); - if (qch.isSpace()) { - return QLatin1Char(' '); - } - - if (qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive)) { - return QLatin1Char('a'); - } - - return qch; - } -} - void TerminalDisplay::setWordCharacters(const QString& wc) { _wordCharacters = wc; + + if (_screenWindow) { + _screenWindow->setWordCharacters(wc); + } } void TerminalDisplay::setUsesMouseTracking(bool on) @@ -3799,8 +3382,9 @@ _screenWindow->screen()->setCurrentTerminalDisplay(this); if (!_readOnly) { - _actSel = 0; // Key stroke implies a screen update, so TerminalDisplay won't - // know where the current selection is. + // Key stroke implies a screen update, so TerminalDisplay won't + // know where the current selection is. + _screenWindow->setSelectionState(0); if (_allowBlinkingCursor) { _blinkCursorTimer->start();