TextEditor: Duplicate selection (Duplicate selection and comment)

New TextEditor action for duplicating current selection.
Extended version of this feature creates commented duplications.

Possible use cases:
1. No selection, cursor anywhere in text - Duplicity line
2. Simple selection - Duplicity selection
3. Block selection, without columns - Duplicity lines
4. Block selection, with columns - Duplicity selection

Cursor position and selection stays unchanged. Works well with Undo
action.

First use case with no selection looks similar as copyLineDown, but
difference is that copyLineDown moves current cursor position and select
created line. This feature don't change cursor position. Because of this
difference it is not possible to integrate this additions with
copyLineDown.

Quick intro: https://youtu.be/Fv6WdCnCLpo

Change-Id: I7c36fca6e17de030cbd22cfa103c2ed672deabbc
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Michal Steller
2016-09-26 15:55:04 +02:00
parent a693bd97ef
commit 768eb4e52e
4 changed files with 140 additions and 0 deletions

View File

@@ -256,6 +256,8 @@ public:
void print(QPrinter *printer); void print(QPrinter *printer);
void maybeSelectLine(); void maybeSelectLine();
void duplicateSelection(bool comment);
void duplicateBlockSelection(bool comment);
void updateCannotDecodeInfo(); void updateCannotDecodeInfo();
void collectToCircularClipboard(); void collectToCircularClipboard();
@@ -6165,6 +6167,128 @@ void TextEditorWidget::copyLine()
copy(); copy();
} }
void TextEditorWidgetPrivate::duplicateSelection(bool comment)
{
if (m_inBlockSelectionMode) {
duplicateBlockSelection(comment);
return;
}
QTextCursor cursor = q->textCursor();
if (cursor.hasSelection()) {
// Cannot "duplicate and comment" files without multi-line comment
if (comment && !m_commentDefinition.hasMultiLineStyle())
return;
QString dupText = cursor.selectedText().replace(QChar::ParagraphSeparator, QLatin1Char('\n'));
if (comment)
dupText = (m_commentDefinition.multiLineStart + dupText + m_commentDefinition.multiLineEnd);
const int selStart = cursor.selectionStart();
const int selEnd = cursor.selectionEnd();
const bool cursorAtStart = (cursor.position() == selStart);
cursor.setPosition(selEnd);
cursor.insertText(dupText);
cursor.setPosition(cursorAtStart ? selEnd : selStart);
cursor.setPosition(cursorAtStart ? selStart : selEnd, QTextCursor::KeepAnchor);
} else {
const int curPos = cursor.position();
const QTextBlock &block = cursor.block();
QString dupText = block.text() + QLatin1Char('\n');
if (comment && m_commentDefinition.hasSingleLineStyle())
dupText.append(m_commentDefinition.singleLine);
cursor.setPosition(block.position());
cursor.insertText(dupText);
cursor.setPosition(curPos);
}
q->setTextCursor(cursor);
}
void TextEditorWidgetPrivate::duplicateBlockSelection(bool comment)
{
QTextCursor cursor = q->textCursor();
const TextBlockSelection curSel = m_blockSelection;
if (curSel.positionColumn == curSel.anchorColumn) {
// No columns selected, duplicating multiple lines
const bool isUp = curSel.positionBlock > curSel.anchorBlock;
const QString commentText =
(comment && m_commentDefinition.hasSingleLineStyle()) ?
m_commentDefinition.singleLine : QString();
QTextBlock block = cursor.block();
QString dupText = commentText + block.text() + QLatin1Char('\n');
for (int b = curSel.firstBlockNumber(); b < curSel.lastBlockNumber(); ++b) {
if (isUp) {
block = block.previous();
dupText.prepend(commentText + block.text() + QLatin1Char('\n'));
} else {
block = block.next();
dupText.append(commentText + block.text() + QLatin1Char('\n'));
}
}
if (isUp)
block = cursor.block();
cursor.setPosition(block.position() + block.length());
cursor.insertText(dupText);
} else {
// Duplicating full block selection with columns
// Cannot "duplicate and comment" files without multi-line comment
if (comment && !m_commentDefinition.hasMultiLineStyle())
return;
const int fc = curSel.firstVisualColumn();
const int lc = curSel.lastVisualColumn();
const int l = lc - fc;
cursor.beginEditBlock();
for (int b = curSel.firstBlockNumber(); b <= curSel.lastBlockNumber(); ++b) {
const QTextBlock &block = m_document->document()->findBlockByNumber(b);
QString dupText = block.text();
const int dupTextLength = dupText.length();
if (dupTextLength < lc) {
const QString addSpace(lc - dupTextLength, ' ');
cursor.setPosition(block.position() + dupTextLength);
cursor.insertText(addSpace);
dupText.append(addSpace);
}
cursor.setPosition(block.position() + lc);
dupText = dupText.mid(fc, l);
if (comment)
dupText = (m_commentDefinition.multiLineStart + dupText + m_commentDefinition.multiLineEnd);
cursor.insertText(dupText);
}
cursor.endEditBlock();
}
enableBlockSelection(curSel.positionBlock, curSel.positionColumn,
curSel.anchorBlock, curSel.anchorColumn);
cursor = m_blockSelection.cursor(m_document.data());
q->doSetTextCursor(cursor, m_blockSelection.hasSelection());
}
void TextEditorWidget::duplicateSelection()
{
d->duplicateSelection(false);
}
void TextEditorWidget::duplicateSelectionAndComment()
{
d->duplicateSelection(true);
}
void TextEditorWidget::deleteLine() void TextEditorWidget::deleteLine()
{ {
d->maybeSelectLine(); d->maybeSelectLine();

View File

@@ -357,6 +357,8 @@ public:
void cutLine(); void cutLine();
void copyLine(); void copyLine();
void duplicateSelection();
void duplicateSelectionAndComment();
void deleteLine(); void deleteLine();
void deleteEndOfWord(); void deleteEndOfWord();
void deleteEndOfWordCamelCase(); void deleteEndOfWordCamelCase();

View File

@@ -142,6 +142,8 @@ public:
QAction *m_unfoldAction; QAction *m_unfoldAction;
QAction *m_cutLineAction; QAction *m_cutLineAction;
QAction *m_copyLineAction; QAction *m_copyLineAction;
QAction *m_duplicateSelectionAction;
QAction *m_duplicateSelectionAndCommentAction;
QAction *m_deleteLineAction; QAction *m_deleteLineAction;
QAction *m_deleteEndOfWordAction; QAction *m_deleteEndOfWordAction;
QAction *m_deleteEndOfWordCamelCaseAction; QAction *m_deleteEndOfWordCamelCaseAction;
@@ -206,6 +208,8 @@ TextEditorActionHandlerPrivate::TextEditorActionHandlerPrivate
m_unfoldAction(0), m_unfoldAction(0),
m_cutLineAction(0), m_cutLineAction(0),
m_copyLineAction(0), m_copyLineAction(0),
m_duplicateSelectionAction(0),
m_duplicateSelectionAndCommentAction(0),
m_deleteLineAction(0), m_deleteLineAction(0),
m_deleteEndOfWordAction(0), m_deleteEndOfWordAction(0),
m_deleteEndOfWordCamelCaseAction(0), m_deleteEndOfWordCamelCaseAction(0),
@@ -403,6 +407,14 @@ void TextEditorActionHandlerPrivate::createActions()
[this] (TextEditorWidget *w) { w->copyLine(); }, false, tr("Copy &Line"), [this] (TextEditorWidget *w) { w->copyLine(); }, false, tr("Copy &Line"),
QKeySequence(tr("Ctrl+Ins")), QKeySequence(tr("Ctrl+Ins")),
G_EDIT_TEXT, advancedEditMenu); G_EDIT_TEXT, advancedEditMenu);
m_duplicateSelectionAction = registerAction(DUPLICATE_SELECTION,
[this] (TextEditorWidget *w) { w->duplicateSelection(); }, false, tr("&Duplicate Selection"),
QKeySequence(),
G_EDIT_TEXT, advancedEditMenu);
m_duplicateSelectionAndCommentAction = registerAction(DUPLICATE_SELECTION_AND_COMMENT,
[this] (TextEditorWidget *w) { w->duplicateSelectionAndComment(); }, false, tr("&Duplicate Selection and Comment"),
QKeySequence(),
G_EDIT_TEXT, advancedEditMenu);
m_upperCaseSelectionAction = registerAction(UPPERCASE_SELECTION, m_upperCaseSelectionAction = registerAction(UPPERCASE_SELECTION,
[this] (TextEditorWidget *w) { w->uppercaseSelection(); }, true, tr("Uppercase Selection"), [this] (TextEditorWidget *w) { w->uppercaseSelection(); }, true, tr("Uppercase Selection"),
QKeySequence(Core::UseMacShortcuts ? tr("Meta+Shift+U") : tr("Alt+Shift+U")), QKeySequence(Core::UseMacShortcuts ? tr("Meta+Shift+U") : tr("Alt+Shift+U")),

View File

@@ -144,6 +144,8 @@ const char UPPERCASE_SELECTION[] = "TextEditor.UppercaseSelection";
const char LOWERCASE_SELECTION[] = "TextEditor.LowercaseSelection"; const char LOWERCASE_SELECTION[] = "TextEditor.LowercaseSelection";
const char CUT_LINE[] = "TextEditor.CutLine"; const char CUT_LINE[] = "TextEditor.CutLine";
const char COPY_LINE[] = "TextEditor.CopyLine"; const char COPY_LINE[] = "TextEditor.CopyLine";
const char DUPLICATE_SELECTION[] = "TextEditor.DuplicateSelection";
const char DUPLICATE_SELECTION_AND_COMMENT[] = "TextEditor.DuplicateSelectionAndComment";
const char DELETE_LINE[] = "TextEditor.DeleteLine"; const char DELETE_LINE[] = "TextEditor.DeleteLine";
const char DELETE_END_OF_WORD[] = "TextEditor.DeleteEndOfWord"; const char DELETE_END_OF_WORD[] = "TextEditor.DeleteEndOfWord";
const char DELETE_END_OF_WORD_CAMEL_CASE[] = "TextEditor.DeleteEndOfWordCamelCase"; const char DELETE_END_OF_WORD_CAMEL_CASE[] = "TextEditor.DeleteEndOfWordCamelCase";