From 87d8adb1c32c3008013b95ed2d6ff87e9a4d921a Mon Sep 17 00:00:00 2001 From: mae Date: Tue, 9 Nov 2010 15:19:14 +0100 Subject: [PATCH] Support camel-case for word-wise movement This was DevDay's 3rd most requested feature Done-with: Erik Verbruggen --- src/plugins/texteditor/basetexteditor.cpp | 381 +++++++++++++++++++++++++++++- src/plugins/texteditor/basetexteditor.h | 5 + 2 files changed, 385 insertions(+), 1 deletion(-) diff --git a/src/plugins/texteditor/basetexteditor.cpp b/src/plugins/texteditor/basetexteditor.cpp index d505d1ebfe..56e3b48d9f 100644 --- a/src/plugins/texteditor/basetexteditor.cpp +++ b/src/plugins/texteditor/basetexteditor.cpp @@ -1077,6 +1077,382 @@ void BaseTextEditor::cleanWhitespace() d->m_document->cleanWhitespace(textCursor()); } + +// could go into QTextCursor... +static QTextLine currentTextLine(const QTextCursor &cursor) +{ + const QTextBlock block = cursor.block(); + if (!block.isValid()) + return QTextLine(); + + const QTextLayout *layout = block.layout(); + if (!layout) + return QTextLine(); + + const int relativePos = cursor.position() - block.position(); + return layout->lineForTextPosition(relativePos); +} + +bool BaseTextEditor::camelCaseLeft(QTextCursor &cursor, QTextCursor::MoveMode mode) +{ + int state = 0; + enum Input { + Input_U, + Input_l, + Input_underscore, + Input_space, + Input_other + }; + + if (!cursor.movePosition(QTextCursor::Left, mode)) + return false; + + forever { + QChar c = characterAt(cursor.position()); + Input input = Input_other; + if (c.isUpper()) + input = Input_U; + else if (c.isLower() || c.isDigit()) + input = Input_l; + else if (c == QLatin1Char('_')) + input = Input_underscore; + else if (c.isSpace() && c != QChar::ParagraphSeparator) + input = Input_space; + else + input = Input_other; + + switch (state) { + case 0: + switch (input) { + case Input_U: + state = 1; + break; + case Input_l: + state = 2; + break; + case Input_underscore: + state = 3; + break; + case Input_space: + state = 4; + break; + default: + cursor.movePosition(QTextCursor::Right, mode); + return cursor.movePosition(QTextCursor::WordLeft, mode); + } + break; + case 1: + switch (input) { + case Input_U: + break; + default: + return true; + } + break; + + case 2: + switch (input) { + case Input_U: + return true; + case Input_l: + break; + default: + cursor.movePosition(QTextCursor::Right, mode); + return true; + } + break; + case 3: + switch (input) { + case Input_underscore: + break; + case Input_U: + state = 1; + break; + case Input_l: + state = 2; + break; + default: + cursor.movePosition(QTextCursor::Right, mode); + return true; + } + break; + case 4: + switch (input) { + case Input_space: + break; + case Input_U: + state = 1; + break; + case Input_l: + state = 2; + break; + case Input_underscore: + state = 3; + break; + default: + cursor.movePosition(QTextCursor::Right, mode); + if (cursor.positionInBlock() == 0) + return true; + return cursor.movePosition(QTextCursor::WordLeft, mode); + } + } + + if (!cursor.movePosition(QTextCursor::Left, mode)) + return true; + } +} + +bool BaseTextEditor::camelCaseRight(QTextCursor &cursor, QTextCursor::MoveMode mode) +{ + int state = 0; + enum Input { + Input_U, + Input_l, + Input_underscore, + Input_space, + Input_other + }; + + forever { + QChar c = characterAt(cursor.position()); + Input input = Input_other; + if (c.isUpper()) + input = Input_U; + else if (c.isLower() || c.isDigit()) + input = Input_l; + else if (c == QLatin1Char('_')) + input = Input_underscore; + else if (c.isSpace() && c != QChar::ParagraphSeparator) + input = Input_space; + else + input = Input_other; + + switch (state) { + case 0: + switch (input) { + case Input_U: + state = 4; + break; + case Input_l: + state = 1; + break; + case Input_underscore: + state = 6; + break; + default: + return cursor.movePosition(QTextCursor::WordRight, mode); + } + break; + case 1: + switch (input) { + case Input_U: + return true; + case Input_l: + break; + case Input_underscore: + state = 6; + break; + case Input_space: + state = 7; + break; + default: + return true; + } + break; + case 2: + switch (input) { + case Input_U: + break; + case Input_l: + cursor.movePosition(QTextCursor::Left, mode); + return true; + case Input_space: + state = 7; + break; + default: + return cursor.movePosition(QTextCursor::WordRight, mode); + } + break; + case 4: + switch (input) { + case Input_U: + state = 2; + break; + case Input_l: + state = 1; + break; + case Input_underscore: + state = 6; + break; + case Input_space: + state = 7; + break; + default: + return cursor.movePosition(QTextCursor::WordRight, mode); + } + break; + case 6: + switch (input) { + case Input_underscore: + break; + case Input_space: + state = 7; + break; + default: + return cursor.movePosition(QTextCursor::WordRight, mode); + } + break; + case 7: + switch (input) { + case Input_space: + break; + default: + return true; + } + break; + } + cursor.movePosition(QTextCursor::Right, mode); + } +} + +bool BaseTextEditor::cursorMoveKeyEvent(QKeyEvent *e) +{ + QTextCursor cursor = textCursor(); + + QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; + QTextCursor::MoveOperation op = QTextCursor::NoMove; + + if (e == QKeySequence::MoveToNextChar) { + op = QTextCursor::Right; + } + else if (e == QKeySequence::MoveToPreviousChar) { + op = QTextCursor::Left; + } + else if (e == QKeySequence::SelectNextChar) { + op = QTextCursor::Right; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousChar) { + op = QTextCursor::Left; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectNextWord) { + op = QTextCursor::WordRight; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousWord) { + op = QTextCursor::WordLeft; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfLine) { + op = QTextCursor::StartOfLine; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfLine) { + op = QTextCursor::EndOfLine; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfBlock) { + op = QTextCursor::StartOfBlock; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfBlock) { + op = QTextCursor::EndOfBlock; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectStartOfDocument) { + op = QTextCursor::Start; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectEndOfDocument) { + op = QTextCursor::End; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectPreviousLine) { + op = QTextCursor::Up; + mode = QTextCursor::KeepAnchor; + } + else if (e == QKeySequence::SelectNextLine) { + op = QTextCursor::Down; + mode = QTextCursor::KeepAnchor; + { + QTextBlock block = cursor.block(); + QTextLine line = currentTextLine(cursor); + if (!block.next().isValid() + && line.isValid() + && line.lineNumber() == block.layout()->lineCount() - 1) + op = QTextCursor::End; + } + } + else if (e == QKeySequence::MoveToNextWord) { + op = QTextCursor::WordRight; + } + else if (e == QKeySequence::MoveToPreviousWord) { + op = QTextCursor::WordLeft; + } + else if (e == QKeySequence::MoveToEndOfBlock) { + op = QTextCursor::EndOfBlock; + } + else if (e == QKeySequence::MoveToStartOfBlock) { + op = QTextCursor::StartOfBlock; + } + else if (e == QKeySequence::MoveToNextLine) { + op = QTextCursor::Down; + } + else if (e == QKeySequence::MoveToPreviousLine) { + op = QTextCursor::Up; + } + else if (e == QKeySequence::MoveToPreviousLine) { + op = QTextCursor::Up; + } + else if (e == QKeySequence::MoveToStartOfLine) { + op = QTextCursor::StartOfLine; + } + else if (e == QKeySequence::MoveToEndOfLine) { + op = QTextCursor::EndOfLine; + } + else if (e == QKeySequence::MoveToStartOfDocument) { + op = QTextCursor::Start; + } + else if (e == QKeySequence::MoveToEndOfDocument) { + op = QTextCursor::End; + } + else { + return false; + } + + +// Except for pageup and pagedown, Mac OS X has very different behavior, we don't do it all, but +// here's the breakdown: +// Shift still works as an anchor, but only one of the other keys can be down Ctrl (Command), +// Alt (Option), or Meta (Control). +// Command/Control + Left/Right -- Move to left or right of the line +// + Up/Down -- Move to top bottom of the file. (Control doesn't move the cursor) +// Option + Left/Right -- Move one word Left/right. +// + Up/Down -- Begin/End of Paragraph. +// Home/End Top/Bottom of file. (usually don't move the cursor, but will select) + + bool visualNavigation = cursor.visualNavigation(); + cursor.setVisualNavigation(true); + bool moved = false; + + if (op == QTextCursor::WordRight) { + moved = camelCaseRight(cursor, mode); + } else if (op == QTextCursor::WordLeft) { + moved = camelCaseLeft(cursor, mode); + } else { + moved = cursor.movePosition(op, mode); + } + cursor.setVisualNavigation(visualNavigation); + + if (moved) { + setTextCursor(cursor); + ensureCursorVisible(); + } + return true; +} + + void BaseTextEditor::keyPressEvent(QKeyEvent *e) { viewport()->setCursor(Qt::BlankCursor); @@ -1335,7 +1711,10 @@ void BaseTextEditor::keyPressEvent(QKeyEvent *e) } if (ro || e->text().isEmpty() || !e->text().at(0).isPrint()) { - QPlainTextEdit::keyPressEvent(e); + if (cursorMoveKeyEvent(e)) + ; + else + QPlainTextEdit::keyPressEvent(e); } else if ((e->modifiers() & (Qt::ControlModifier|Qt::AltModifier)) != Qt::ControlModifier){ QTextCursor cursor = textCursor(); QString text = e->text(); diff --git a/src/plugins/texteditor/basetexteditor.h b/src/plugins/texteditor/basetexteditor.h index b17199d972..9296b58126 100644 --- a/src/plugins/texteditor/basetexteditor.h +++ b/src/plugins/texteditor/basetexteditor.h @@ -525,6 +525,11 @@ private: void universalHelper(); // test function for development + bool cursorMoveKeyEvent(QKeyEvent *e); + bool camelCaseRight(QTextCursor &cursor, QTextCursor::MoveMode mode); + bool camelCaseLeft(QTextCursor &cursor, QTextCursor::MoveMode mode); + + private slots: // auto completion void _q_requestAutoCompletion(); -- 2.11.0