Commit 29b073e9 authored by mae's avatar mae

Refactor block selection

Block selection was "broken" when using tabs, or rather
incomplete: It treated tabs as normal characters, which
has shown to be unexpected by people using tabs in code.

The new implementation has a vastly improved find scope
as well. In addition, creating a blog selection with
mouse or keyboard feels a lot more solid now, as the
actual selection is detached from possible valid cursor
positions.

Task-number: QTCREATORBUG-1541
parent 9b338fbb
......@@ -39,14 +39,16 @@ using namespace Find;
BaseTextFind::BaseTextFind(QTextEdit *editor)
: m_editor(editor)
, m_findScopeVerticalBlockSelection(0)
, m_findScopeVerticalBlockSelectionFirstColumn(-1)
, m_findScopeVerticalBlockSelectionLastColumn(-1)
, m_incrementalStartPos(-1)
{
}
BaseTextFind::BaseTextFind(QPlainTextEdit *editor)
: m_plaineditor(editor)
, m_findScopeVerticalBlockSelection(0)
, m_findScopeVerticalBlockSelectionFirstColumn(-1)
, m_findScopeVerticalBlockSelectionLastColumn(-1)
, m_incrementalStartPos(-1)
{
}
......@@ -268,17 +270,16 @@ QTextCursor BaseTextFind::findOne(const QRegExp &expr, const QTextCursor &from,
if (candidate.isNull())
return candidate;
if (!m_findScopeVerticalBlockSelection)
if (m_findScopeVerticalBlockSelectionFirstColumn < 0)
return candidate;
forever {
if (!inScope(candidate.selectionStart(), candidate.selectionEnd()))
return candidate;
QTextCursor b = candidate;
b.setPosition(candidate.selectionStart());
QTextCursor e = candidate;
e.setPosition(candidate.selectionEnd());
if (b.positionInBlock() >= m_findScopeStart.positionInBlock() + 1
&& e.positionInBlock() <= m_findScopeStart.positionInBlock() + 1 + m_findScopeVerticalBlockSelection)
bool inVerticalFindScope = false;
QMetaObject::invokeMethod(m_plaineditor, "inFindScope", Qt::DirectConnection,
Q_RETURN_ARG(bool, inVerticalFindScope),
Q_ARG(QTextCursor, candidate));
if (inVerticalFindScope)
return candidate;
candidate = document()->find(expr, candidate, options);
}
......@@ -299,23 +300,19 @@ void BaseTextFind::defineFindScope()
if (cursor.hasSelection() && cursor.block() != cursor.document()->findBlock(cursor.anchor())) {
m_findScopeStart = QTextCursor(document()->docHandle(), qMax(0, cursor.selectionStart()-1));
m_findScopeEnd = QTextCursor(document()->docHandle(), cursor.selectionEnd());
m_findScopeVerticalBlockSelection = 0;
int verticalBlockSelection = 0;
if (m_plaineditor && m_plaineditor->metaObject()->indexOfProperty("verticalBlockSelection") >= 0)
verticalBlockSelection = m_plaineditor->property("verticalBlockSelection").toInt();
if (verticalBlockSelection) {
QTextCursor findScopeVisualStart(document()->docHandle(), cursor.selectionStart());
int findScopeFromColumn = qMin(findScopeVisualStart.positionInBlock(),
m_findScopeEnd.positionInBlock());
int findScopeToColumn = findScopeFromColumn + verticalBlockSelection;
m_findScopeStart.setPosition(findScopeVisualStart.block().position() + findScopeFromColumn - 1);
m_findScopeEnd.setPosition(m_findScopeEnd.block().position()
+ qMin(m_findScopeEnd.block().length()-1, findScopeToColumn));
m_findScopeVerticalBlockSelection = verticalBlockSelection;
m_findScopeVerticalBlockSelectionFirstColumn = -1;
m_findScopeVerticalBlockSelectionLastColumn = -1;
if (m_plaineditor && m_plaineditor->metaObject()->indexOfProperty("verticalBlockSelectionFirstColumn") >= 0) {
m_findScopeVerticalBlockSelectionFirstColumn
= m_plaineditor->property("verticalBlockSelectionFirstColumn").toInt();
m_findScopeVerticalBlockSelectionLastColumn
= m_plaineditor->property("verticalBlockSelectionLastColumn").toInt();
}
emit findScopeChanged(m_findScopeStart, m_findScopeEnd, m_findScopeVerticalBlockSelection);
emit findScopeChanged(m_findScopeStart, m_findScopeEnd,
m_findScopeVerticalBlockSelectionFirstColumn,
m_findScopeVerticalBlockSelectionLastColumn);
cursor.setPosition(m_findScopeStart.position()+1);
setTextCursor(cursor);
} else {
......@@ -327,6 +324,9 @@ void BaseTextFind::clearFindScope()
{
m_findScopeStart = QTextCursor();
m_findScopeEnd = QTextCursor();
m_findScopeVerticalBlockSelection = 0;
emit findScopeChanged(m_findScopeStart, m_findScopeEnd, m_findScopeVerticalBlockSelection);
m_findScopeVerticalBlockSelectionFirstColumn = -1;
m_findScopeVerticalBlockSelectionLastColumn = -1;
emit findScopeChanged(m_findScopeStart, m_findScopeEnd,
m_findScopeVerticalBlockSelectionFirstColumn,
m_findScopeVerticalBlockSelectionLastColumn);
}
......@@ -72,7 +72,9 @@ public:
signals:
void highlightAll(const QString &txt, Find::FindFlags findFlags);
void findScopeChanged(const QTextCursor &start, const QTextCursor &end, int verticalBlockSelection);
void findScopeChanged(const QTextCursor &start, const QTextCursor &end,
int verticalBlockSelectionFirstColumn,
int verticalBlockSelectionLastColumn);
private:
bool find(const QString &txt,
......@@ -89,7 +91,8 @@ private:
QPointer<QPlainTextEdit> m_plaineditor;
QTextCursor m_findScopeStart;
QTextCursor m_findScopeEnd;
int m_findScopeVerticalBlockSelection;
int m_findScopeVerticalBlockSelectionFirstColumn;
int m_findScopeVerticalBlockSelectionLastColumn;
bool inScope(int startPosition, int endPosition) const;
QTextCursor findOne(const QRegExp &expr, const QTextCursor &from, QTextDocument::FindFlags options) const;
int m_incrementalStartPos;
......
......@@ -591,7 +591,11 @@ void OutputWindow::appendText(const QString &textIn, const QTextCharFormat &form
bool OutputWindow::isScrollbarAtBottom() const
{
return verticalScrollBar()->value() == verticalScrollBar()->maximum();
return isVisible()
&& (blockBoundingRect(document()->lastBlock()).bottom()
+ contentOffset().y() <= viewport()->rect().bottom());
// return verticalScrollBar()->value() == verticalScrollBar()->maximum();
}
void OutputWindow::scrollToBottom()
......
This diff is collapsed.
......@@ -47,6 +47,7 @@ namespace Utils {
}
namespace TextEditor {
struct TabSettings;
namespace Internal {
class BaseTextEditorPrivate;
......@@ -55,6 +56,28 @@ namespace Internal {
struct RefactorMarker;
typedef QList<RefactorMarker> RefactorMarkers;
class TEXTEDITOR_EXPORT BaseTextBlockSelection
{
public:
bool isValid() const{ return !firstBlock.isNull() && !lastBlock.isNull(); }
void clear() { firstBlock = lastBlock = QTextCursor(); }
QTextCursor firstBlock; // defines the first block
QTextCursor lastBlock; // defines the last block
int firstVisualColumn; // defines the first visual column of the selection
int lastVisualColumn; // defines the last visual column of the selection
enum Anchor {TopLeft = 0, TopRight, BottomLeft, BottomRight} anchor;
BaseTextBlockSelection():firstVisualColumn(0), lastVisualColumn(0), anchor(BottomRight){}
void moveAnchor(int blockNumber, int visualColumn);
inline int anchorColumnNumber() const { return (anchor % 2) ? lastVisualColumn : firstVisualColumn; }
inline int anchorBlockNumber() const {
return (anchor <= TopRight ? firstBlock.blockNumber() : lastBlock.blockNumber()); }
QTextCursor selection(const TabSettings &ts) const;
void fromSelection(const TabSettings &ts, const QTextCursor &selection);
};
}
class ITextMarkable;
......@@ -66,8 +89,6 @@ struct BehaviorSettings;
struct CompletionSettings;
struct DisplaySettings;
struct StorageSettings;
struct TabSettings;
class TEXTEDITOR_EXPORT BaseTextEditorAnimator : public QObject
{
......@@ -113,8 +134,8 @@ private:
class TEXTEDITOR_EXPORT BaseTextEditor : public QPlainTextEdit
{
Q_OBJECT
Q_PROPERTY(int verticalBlockSelection READ verticalBlockSelection)
Q_PROPERTY(int verticalBlockSelectionFirstColumn READ verticalBlockSelectionFirstColumn)
Q_PROPERTY(int verticalBlockSelectionLastColumn READ verticalBlockSelectionLastColumn)
public:
BaseTextEditor(QWidget *parent);
~BaseTextEditor();
......@@ -214,7 +235,8 @@ public:
void insertCodeSnippet(const QTextCursor &cursor, const QString &snippet);
int verticalBlockSelection() const;
int verticalBlockSelectionFirstColumn() const;
int verticalBlockSelectionLastColumn() const;
QRegion translatedLineRegion(int lineStart, int lineEnd) const;
......@@ -315,7 +337,9 @@ private slots:
void documentAboutToBeReloaded();
void documentReloaded();
void highlightSearchResults(const QString &txt, Find::FindFlags findFlags);
void setFindScope(const QTextCursor &start, const QTextCursor &end, int);
void setFindScope(const QTextCursor &start, const QTextCursor &end, int, int);
bool inFindScope(const QTextCursor &cursor);
bool inFindScope(int selectionStart, int selectionEnd);
void currentEditorChanged(Core::IEditor *editor);
void maybeEmitContentsChangedBecauseOfUndo();
......@@ -532,6 +556,8 @@ private slots:
// auto completion
void _q_requestAutoCompletion();
void handleBlockSelection(int diff_row, int diff_col);
// parentheses matcher
void _q_matchParentheses();
void _q_highlightBlocks();
......
......@@ -245,8 +245,6 @@ public:
// block selection mode
bool m_inBlockSelectionMode;
bool m_lastEventWasBlockSelectionEvent;
int m_blockSelectionExtraX;
void clearBlockSelection();
QString copyBlockSelection();
void removeBlockSelection(const QString &text = QString());
......@@ -254,9 +252,13 @@ public:
QTextCursor m_findScopeStart;
QTextCursor m_findScopeEnd;
int m_findScopeVerticalBlockSelection;
int m_findScopeVerticalBlockSelectionFirstColumn;
int m_findScopeVerticalBlockSelectionLastColumn;
QTextCursor m_selectBlockAnchor;
Internal::BaseTextBlockSelection m_blockSelection;
void moveCursorVisible(bool ensureVisible = true);
int visualIndent(const QTextBlock &block) const;
......
......@@ -226,6 +226,22 @@ int TabSettings::columnAt(const QString &text, int position) const
return column;
}
int TabSettings::positionAtColumn(const QString &text, int column, int *offset) const
{
int col = 0;
int i = 0;
while (i < text.size() && col < column) {
if (text.at(i) == QLatin1Char('\t'))
col = col - (col % m_tabSize) + m_tabSize;
else
++col;
++i;
}
if (offset)
*offset = column - col;
return i;
}
int TabSettings::spacesLeftFromPosition(const QString &text, int position) const
{
int i = position;
......
......@@ -61,6 +61,7 @@ struct TEXTEDITOR_EXPORT TabSettings
int firstNonSpace(const QString &text) const;
inline bool onlySpace(const QString &text) const { return firstNonSpace(text) == text.length(); }
int columnAt(const QString &text, int position) const;
int positionAtColumn(const QString &text, int column, int *offset = 0) const;
int spacesLeftFromPosition(const QString &text, int position) const;
int indentedColumn(int column, bool doIndent = true) const;
QString indentationString(int startColumn, int targetColumn, const QTextBlock &currentBlock = QTextBlock()) const;
......
......@@ -72,8 +72,7 @@ void TextEditorOverlay::clear()
void TextEditorOverlay::addOverlaySelection(int begin, int end,
const QColor &fg, const QColor &bg,
uint overlaySelectionFlags,
int verticalBlockSelection)
uint overlaySelectionFlags)
{
if (end < begin)
return;
......@@ -84,14 +83,15 @@ void TextEditorOverlay::addOverlaySelection(int begin, int end,
selection.m_fg = fg;
selection.m_bg = bg;
selection.m_cursor_begin = QTextCursor(document->docHandle(), begin);
selection.m_cursor_end = QTextCursor(document->docHandle(), end);
if (overlaySelectionFlags & ExpandBegin) {
if (begin > 0 && begin < end) { // not empty
selection.m_expandBegin = true;
selection.m_cursor_begin.setKeepPositionOnInsert(true);
}
}
selection.m_cursor_begin = QTextCursor(document->docHandle(), begin);
selection.m_cursor_end = QTextCursor(document->docHandle(), end);
if (overlaySelectionFlags & LockSize)
selection.m_fixedLength = (end - begin);
......@@ -99,8 +99,6 @@ void TextEditorOverlay::addOverlaySelection(int begin, int end,
selection.m_dropShadow = (overlaySelectionFlags & DropShadow);
selection.m_verticalBlockSelection = verticalBlockSelection;
m_selections.append(selection);
update();
}
......@@ -108,11 +106,9 @@ void TextEditorOverlay::addOverlaySelection(int begin, int end,
void TextEditorOverlay::addOverlaySelection(const QTextCursor &cursor,
const QColor &fg, const QColor &bg,
uint overlaySelectionFlags,
int verticalBlockSelection)
uint overlaySelectionFlags)
{
addOverlaySelection(cursor.selectionStart(), cursor.selectionEnd(), fg, bg, overlaySelectionFlags,
verticalBlockSelection);
addOverlaySelection(cursor.selectionStart(), cursor.selectionEnd(), fg, bg, overlaySelectionFlags);
}
QRect TextEditorOverlay::rect() const
......@@ -121,7 +117,7 @@ QRect TextEditorOverlay::rect() const
}
QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, const QTextCursor &end,
const QRect &clip, int verticalBlockSelection)
const QRect &clip)
{
if (begin.isNull() || end.isNull() || begin.position() > end.position())
return QPainterPath();
......@@ -173,8 +169,6 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co
line = blockLayout->lineForTextPosition(beginChar);
inSelection = true;
firstOrLastBlock = true;
} else if (verticalBlockSelection) {
beginChar = qMin(block.length()-1, begin.positionInBlock());
} else {
while (beginChar < block.length() && document->characterAt(block.position() + beginChar).isSpace())
++beginChar;
......@@ -189,8 +183,6 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co
lastLine = blockLayout->lineForTextPosition(endChar).lineNumber();
inSelection = false;
firstOrLastBlock = true;
} else if (verticalBlockSelection) {
endChar = qMin(block.length()-1, begin.positionInBlock() + verticalBlockSelection);
} else {
endChar = block.length();
while (endChar > beginChar && document->characterAt(block.position() + endChar - 1).isSpace())
......@@ -211,7 +203,7 @@ QPainterPath TextEditorOverlay::createSelectionPath(const QTextCursor &begin, co
lineRect.setRight(line.cursorToX(endChar));
selection += lineRect.translated(blockGeometry.topLeft());
}
} else if (!verticalBlockSelection){ // empty lines
} else { // empty lines
const int emptyLineSelectionSize = 16;
if (!firstOrLastBlock && !selection.isEmpty()) { // middle
lineRect.setLeft(selection.last().left());
......@@ -315,8 +307,6 @@ void TextEditorOverlay::paintSelection(QPainter *painter,
{
QTextCursor begin = selection.m_cursor_begin;
if (selection.m_expandBegin)
begin.setPosition(begin.position() + 1);
const QTextCursor &end= selection.m_cursor_end;
const QColor &fg = selection.m_fg;
......@@ -328,8 +318,7 @@ void TextEditorOverlay::paintSelection(QPainter *painter,
|| begin.position() > end.position())
return;
QPainterPath path = createSelectionPath(begin, end, m_editor->viewport()->rect(),
selection.m_verticalBlockSelection);
QPainterPath path = createSelectionPath(begin, end, m_editor->viewport()->rect());
painter->save();
QColor penColor = fg;
......@@ -389,8 +378,7 @@ void TextEditorOverlay::fillSelection(QPainter *painter,
if (begin.isNull() || end.isNull() || begin.position() > end.position())
return;
QPainterPath path = createSelectionPath(begin, end, m_editor->viewport()->rect(),
selection.m_verticalBlockSelection);
QPainterPath path = createSelectionPath(begin, end, m_editor->viewport()->rect());
painter->save();
painter->translate(-.5, -.5);
......
......@@ -37,17 +37,14 @@ namespace TextEditor {
namespace Internal {
struct TEXTEDITOR_EXPORT OverlaySelection {
OverlaySelection():m_fixedLength(-1), m_dropShadow(false),
m_expandBegin(false), m_verticalBlockSelection(0){}
OverlaySelection():m_fixedLength(-1), m_dropShadow(false){}
QTextCursor m_cursor_begin;
QTextCursor m_cursor_end;
QColor m_fg;
QColor m_bg;
int m_fixedLength;
bool m_dropShadow;
bool m_expandBegin;
int m_verticalBlockSelection;
};
};
class TEXTEDITOR_EXPORT TextEditorOverlay : public QObject
{
......@@ -92,9 +89,9 @@ public:
};
void addOverlaySelection(const QTextCursor &cursor, const QColor &fg, const QColor &bg,
uint overlaySelectionFlags = 0, int verticalBlockSelection = 0);
uint overlaySelectionFlags = 0);
void addOverlaySelection(int begin, int end, const QColor &fg, const QColor &bg,
uint overlaySelectionFlags = 0, int verticalBlockSelection = 0);
uint overlaySelectionFlags = 0);
inline bool isEmpty() const { return m_selections.isEmpty(); }
......@@ -103,8 +100,7 @@ public:
bool hasCursorInSelection(const QTextCursor &cursor) const;
private:
QPainterPath createSelectionPath(const QTextCursor &begin, const QTextCursor &end, const QRect& clip,
int verticalBlockSelection);
QPainterPath createSelectionPath(const QTextCursor &begin, const QTextCursor &end, const QRect& clip);
void paintSelection(QPainter *painter, const OverlaySelection &selection);
void fillSelection(QPainter *painter, const OverlaySelection &selection, const QColor &color);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment