From 743b7c8e285bbd832bda0437340ae2ec66acb08a Mon Sep 17 00:00:00 2001 From: Ivan Pelipenko Date: Tue, 22 Sep 2020 20:45:23 +0300 Subject: [PATCH] QCodeEdit blockselection almost works, without copypaste and with bad visual --- libs/widgets/qcodeedit.cpp | 434 +++++++++++++++++++++++++------------ libs/widgets/qcodeedit.h | 12 +- 2 files changed, 309 insertions(+), 137 deletions(-) diff --git a/libs/widgets/qcodeedit.cpp b/libs/widgets/qcodeedit.cpp index 3626293..983571c 100644 --- a/libs/widgets/qcodeedit.cpp +++ b/libs/widgets/qcodeedit.cpp @@ -49,7 +49,7 @@ QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent) { overlay = 0; prev_lc = auto_comp_pl = cur_search_ind = pos_press = pos_el_press = -1; timer_parse = 0; - _ignore_focus_out = _destructor = _replacing = cursor_state = false; + _ignore_focus_out = _destructor = _replacing = cursor_state = block_sel_state = false; _first = true; qRegisterMetaType(); qRegisterMetaType(); @@ -454,21 +454,20 @@ bool QCodeEdit::eventFilter(QObject * o, QEvent * e) { if (ui->textCode) { if (o == ui->textCode->viewport()) { if (e->type() == QEvent::MouseButtonPress) { + cancelBlockSelection(); completer->hide(); hideHelp(); - } - if (e->type() == QEvent::MouseMove && completer->isHidden()) { - QMouseEvent * me = (QMouseEvent*)e; - if (me->modifiers().testFlag(Qt::ControlModifier)) { - showLink(); - } - } - if (e->type() == QEvent::MouseButtonPress) { QMouseEvent * me = (QMouseEvent*)e; if (me->modifiers().testFlag(Qt::ControlModifier) && (me->button() == Qt::LeftButton)) { gotoLink(); } } + if (e->type() == QEvent::MouseMove && completer->isHidden()) { + QMouseEvent * me = (QMouseEvent*)e; + switchBlockSelection(); + if (me->modifiers().testFlag(Qt::ControlModifier)) + showLink(); + } if (e->type() == QEvent::Paint) { resizeOverlay(); } @@ -477,8 +476,6 @@ bool QCodeEdit::eventFilter(QObject * o, QEvent * e) { if (o == ui->textCode) { //qDebug() << e; QMetaObject::invokeMethod(this, "syncScrolls", Qt::QueuedConnection); - QKeyEvent * ke; - QChar kc(0); switch (e->type()) { case QEvent::ToolTip: if (completer->isHidden()) { @@ -488,112 +485,9 @@ bool QCodeEdit::eventFilter(QObject * o, QEvent * e) { } break; case QEvent::KeyPress: - ke = (QKeyEvent * )e; //qDebug() << "key" << ke; - switch (ke->key()) { - case Qt::Key_Space: - if (ke->modifiers().testFlag(Qt::ControlModifier)) { - invokeAutoCompletition(true); - return true; - } - break; - case Qt::Key_Escape: - hideHelp(); - if (completer->isVisible()) - completer->hide(); - else - hideSearch(); - break; - case Qt::Key_Up: - if (completer->isVisible()) { - completer->previousCompletition(); - return true; - } - completer->hide(); - hideHelp(); - if (ke->modifiers().testFlag(Qt::AltModifier)) { - copyLineUp(); - return true; - } - if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) { - moveLineUp(); - return true; - } - break; - case Qt::Key_Down: - if (completer->isVisible()) { - completer->nextCompletition(); - return true; - } - completer->hide(); - hideHelp(); - if (ke->modifiers().testFlag(Qt::AltModifier)) { - copyLineDown(); - return true; - } - if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) { - moveLineDown(); - return true; - } - break; - case Qt::Key_Home: - case Qt::Key_End: - case Qt::Key_PageUp: - case Qt::Key_PageDown: - if (completer->isVisible()) { - qApp->sendEvent(completer, new QKeyEvent(e->type(), ke->key(), ke->modifiers())); - return true; - } - break; - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_Backspace: - case Qt::Key_Delete: - if (completer->isVisible()) - QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); - break; - case Qt::Key_Return: - if (completer->isVisible()) { - commitCompletition(); - completer->hide(); - return true; - } - if (ui->textCode->textCursor().selectedText().isEmpty()) - QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection); - break; - case Qt::Key_Tab: - if (!ui->textCode->textCursor().selectedText().isEmpty()) { - if (ke->modifiers().testFlag(Qt::ShiftModifier)) - deindent(); - else - indent(); - return true; - } - break; - case Qt::Key_D: - if (ke->modifiers().testFlag(Qt::ControlModifier)) { - completer->hide(); - return true; - } - break; - case Qt::Key_Control: - showLink(); - break; - case Qt::Key_F1: - if (widget_help->isVisible()) - gotoHelpHRef(help_entry); - break; - default: break; - } - if (!ke->text().isEmpty()) - kc = ke->text()[0]; - if (kc == '.') { - completer->hide(); - QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); - } else { - if ((kc.isLetterOrNumber() || kc.toLatin1() == '_') && completer->isVisible()) - QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); - } + if (codeKeyEvent((QKeyEvent * )e)) + return true; break; case QEvent::KeyRelease: hideLink(); @@ -661,6 +555,138 @@ void QCodeEdit::changeEvent(QEvent * e) { } +bool QCodeEdit::codeKeyEvent(QKeyEvent * ke) { + QChar kc; + switch (ke->key()) { + case Qt::Key_Space: + if (ke->modifiers().testFlag(Qt::ControlModifier)) { + invokeAutoCompletition(true); + return true; + } + break; + case Qt::Key_Escape: + hideHelp(); + if (completer->isVisible()) + completer->hide(); + else + hideSearch(); + break; + case Qt::Key_Up: + switchBlockSelection(ke); + if (completer->isVisible()) { + completer->previousCompletition(); + return true; + } + completer->hide(); + hideHelp(); + if (ke->modifiers().testFlag(Qt::AltModifier)) { + if (ke->modifiers().testFlag(Qt::ShiftModifier)) + return false; + else + copyLineUp(); + return true; + } + if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) { + moveLineUp(); + return true; + } + break; + case Qt::Key_Down: + switchBlockSelection(ke); + if (completer->isVisible()) { + completer->nextCompletition(); + return true; + } + completer->hide(); + hideHelp(); + if (ke->modifiers().testFlag(Qt::AltModifier)) { + if (ke->modifiers().testFlag(Qt::ShiftModifier)) + return false; + else + copyLineDown(); + return true; + } + if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) { + moveLineDown(); + return true; + } + break; + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + switchBlockSelection(ke); + if (completer->isVisible()) { + qApp->sendEvent(completer, new QKeyEvent(ke->type(), ke->key(), ke->modifiers())); + return true; + } + break; + case Qt::Key_Left: + case Qt::Key_Right: + switchBlockSelection(ke); + if (hasBlockSelection()) break; + case Qt::Key_Backspace: + case Qt::Key_Delete: + if (completer->isVisible()) + QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); + if (removeBlockSelection(ke->key() == Qt::Key_Delete)) + return true; + break; + case Qt::Key_Return: + if (hasBlockSelection()) { + cancelBlockSelection(); + break; + } + if (completer->isVisible()) { + commitCompletition(); + completer->hide(); + return true; + } + if (ui->textCode->textCursor().selectedText().isEmpty()) + QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection); + break; + case Qt::Key_Tab: + if (!ui->textCode->textCursor().selectedText().isEmpty()) { + if (ke->modifiers().testFlag(Qt::ShiftModifier)) + deindent(); + else + indent(); + return true; + } + break; + case Qt::Key_D: + if (ke->modifiers().testFlag(Qt::ControlModifier)) { + completer->hide(); + return true; + } + break; + case Qt::Key_Control: + showLink(); + break; + case Qt::Key_F1: + if (widget_help->isVisible()) + gotoHelpHRef(help_entry); + break; + default: break; + } + if (!ke->text().isEmpty()) { + if (hasBlockSelection() && (ke->modifiers() == 0 || ke->modifiers() == Qt::ShiftModifier)) { + insertBlockSelection(ke->text()); + return true; + } + kc = ke->text()[0]; + } + if (kc == '.') { + completer->hide(); + QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); + } else { + if ((kc.isLetterOrNumber() || kc.toLatin1() == '_') && completer->isVisible()) + QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); + } + return false; +} + + char antiBracket(char c) { switch (c) { case '(': return ')'; @@ -747,34 +773,143 @@ int QCodeEdit::searchIndFromCursor() { } +QRect QCodeEdit::cursorRect(QRect * line) { + QRect r = ui->textCode->cursorRect(textCursor()), lr = r; + if (hasBlockSelection()) { + r |= ui->textCode->cursorRect(block_start_cursor); + lr.setY(r.y()); + lr.setHeight(r.height()); + } + lr.setWidth(cursor_width); + if (line) *line = lr; + return (r | lr); +} + + +QRect QCodeEdit::blockSelectionRect() { + QTextCursor tc = ui->textCode->textCursor(); + QPoint ps(block_start_cursor.columnNumber(), block_start_cursor.blockNumber()), + pe(tc.columnNumber(), tc.blockNumber()); + QRect bsr(QPoint(qMin(ps.x(), pe.x()), qMin(ps.y(), pe.y())), + QSize(qAbs(ps.x() - pe.x()), qAbs(ps.y() - pe.y()) + 1)); + return bsr; +} + + void QCodeEdit::repaintCursor() { - QRect r = ui->textCode->cursorRect(ui->textCode->textCursor()); - r.setWidth(cursor_width); - //r.translate(-cursor_width, 0); - overlay->update(r); + overlay->update(cursorRect()); } void QCodeEdit::drawCursor() { QPainter p(overlay); QTextCursor tc = textCursor(); - if (cursor_state && ui->textCode->hasFocus()) { - QTextLayout * lay = tc.block().layout(); - if (lay) { - //QTextLine l = lay->lineForTextPosition(tc.positionInBlock()); - //int x = l.cursorToX(tc.position(), QTextLine::Trailing); - p.setPen(Qt::black); - QRect r = ui->textCode->cursorRect(tc); - r.setWidth(cursor_width); - r.adjust(0, 1, 0, -1); - //r.translate(-cursor_width, 0); - p.setCompositionMode(QPainter::CompositionMode_Difference); - p.fillRect(r, Qt::white); - //p.fillRect(p.viewport(), Qt::red); - //qDebug() << x << l.y() << cursor_width << l.height(); - //p.fillRect(x, l.y(), cursor_width, l.height(), Qt::black); - } + //qDebug() << block_start_cursor.position() << tc.position(); + QRect line, all = cursorRect(&line); + if (hasBlockSelection() && (tc.columnNumber() != block_start_cursor.columnNumber())) { + p.setCompositionMode(QPainter::CompositionMode_Difference); + QColor hc = palette().color(QPalette::Highlight); + p.fillRect(all, QColor(255 - hc.red(), 255 - hc.green(), 255 - hc.blue())); } + if (cursor_state && ui->textCode->hasFocus()) { + line.adjust(0, 1, 0, -1); + p.setCompositionMode(QPainter::CompositionMode_Difference); + p.fillRect(line, Qt::white); + } +} + + +bool QCodeEdit::hasBlockSelection() const { + return !block_start_cursor.isNull(); +} + + +void QCodeEdit::startBlockSelection() { + if (!hasBlockSelection()) { + QTextCursor tc = textCursor(); + block_start_cursor = tc; + block_start_cursor.setPosition(tc.selectionStart()); + }} + + +void QCodeEdit::cancelBlockSelection() { + block_start_cursor = QTextCursor(); +} + + +void QCodeEdit::switchBlockSelection(QKeyEvent * ke) { + bool alt = QApplication::keyboardModifiers().testFlag(Qt::AltModifier); + if (ke) alt = ke->modifiers().testFlag(Qt::AltModifier); + if (alt) { + startBlockSelection(); + QTextCursor tc = ui->textCode->textCursor(); + QTextCursor::MoveOperation op = QTextCursor::NoMove; + if (!ke) return; + switch (ke->key()) { + case Qt::Key_Left : op = QTextCursor::Left ; break; + case Qt::Key_Right: op = QTextCursor::Right; break; + case Qt::Key_Up : op = QTextCursor::Up ; break; + case Qt::Key_Down : op = QTextCursor::Down ; break; + default: break; + } + if (op != QTextCursor::NoMove) { + tc.movePosition(op); + ui->textCode->setTextCursor(tc); + } + repaintCursor(); + } else + cancelBlockSelection(); +} + + +bool QCodeEdit::removeBlockSelection(bool is_del) { + if (!hasBlockSelection()) return false; + QTextCursor tc = ui->textCode->textCursor(); + tc.beginEditBlock(); + QRect bsr = blockSelectionRect(); + if (bsr.width() == 0) { + if (is_del) bsr.setWidth(1); + else if (bsr.x() > 0) bsr.setLeft(bsr.x() - 1); + } + //qDebug() << bsr; + for (int l = bsr.top(); l <= bsr.bottom(); ++l) { + QTextCursor ctc(ui->textCode->document()->findBlockByNumber(l)); + ctc.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, bsr.left()); + if (l != ctc.blockNumber()) continue; + int pos = ctc.position(); + ctc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, bsr.width()); + if (l != ctc.blockNumber()) { + ctc.setPosition(pos); + ctc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + } + ctc.removeSelectedText(); + } + tc.setPosition(tc.block().position() + block_start_cursor.positionInBlock()); + ui->textCode->setTextCursor(tc); + tc.endEditBlock(); + return true; +} + + +void QCodeEdit::insertBlockSelection(QString text) { + if (!hasBlockSelection()) return; + QTextCursor tc = ui->textCode->textCursor(); + tc.beginEditBlock(); + QRect bsr = blockSelectionRect(); + if (bsr.width() > 0) removeBlockSelection(false); + for (int l = bsr.top(); l <= bsr.bottom(); ++l) { + QTextCursor ctc(ui->textCode->document()->findBlockByNumber(l)); + ctc.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, bsr.left()); + if (l != ctc.blockNumber()) { + ctc = QTextCursor(ui->textCode->document()->findBlockByNumber(l)); + ctc.movePosition(QTextCursor::EndOfLine); + ctc.insertText(QString(bsr.left() - ctc.columnNumber(), QChar(' '))); + } + ctc.insertText(text); + } + //tc.setPosition(tc.block().position() + block_start_cursor.positionInBlock()); + //ui->textCode->setTextCursor(tc); + tc.endEditBlock(); } @@ -835,6 +970,7 @@ void QCodeEdit::scrollDown() { void QCodeEdit::deleteLine() { QTextCursor tc = ui->textCode->textCursor(); + tc.beginEditBlock(); tc.movePosition(QTextCursor::EndOfLine); tc.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); bool md = true; @@ -848,11 +984,13 @@ void QCodeEdit::deleteLine() { tc.movePosition(QTextCursor::StartOfLine); if (md) tc.movePosition(QTextCursor::Down); ui->textCode->setTextCursor(tc); + tc.endEditBlock(); } void QCodeEdit::copyLineUp() { QTextCursor tc = ui->textCode->textCursor(); + tc.beginEditBlock(); int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se; QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { @@ -875,11 +1013,13 @@ void QCodeEdit::copyLineUp() { tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); + tc.endEditBlock(); } void QCodeEdit::copyLineDown() { QTextCursor tc = ui->textCode->textCursor(); + tc.beginEditBlock(); int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se; QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { @@ -905,6 +1045,7 @@ void QCodeEdit::copyLineDown() { tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); + tc.endEditBlock(); } @@ -921,6 +1062,7 @@ void QCodeEdit::moveLineUp() { tc.setPosition(ss); if (!tc.movePosition(QTextCursor::Up)) return; + tc.beginEditBlock(); tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); QString l = tc.selectedText(); ss -= l.size(); se -= l.size(); @@ -943,6 +1085,7 @@ void QCodeEdit::moveLineUp() { tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); + tc.endEditBlock(); } @@ -959,6 +1102,7 @@ void QCodeEdit::moveLineDown() { tc.setPosition(se); if (!tc.movePosition(QTextCursor::Right)) return; + tc.beginEditBlock(); bool de = false; if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor)) { tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); @@ -981,11 +1125,13 @@ void QCodeEdit::moveLineDown() { tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); + tc.endEditBlock(); } void QCodeEdit::indent() { QTextCursor tc = ui->textCode->textCursor(); + tc.beginEditBlock(); int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se; QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { @@ -1005,11 +1151,13 @@ void QCodeEdit::indent() { tc.setPosition(ss_ + 1); tc.setPosition(se_, QTextCursor::KeepAnchor); ui->textCode->setTextCursor(tc); + tc.endEditBlock(); } void QCodeEdit::deindent() { QTextCursor tc = ui->textCode->textCursor(); + tc.beginEditBlock(); int ss = tc.selectionStart(), ss_ = ss, se = tc.selectionEnd(), se_ = se; QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { @@ -1049,6 +1197,7 @@ void QCodeEdit::deindent() { tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); ui->textCode->setTextCursor(tc); + tc.endEditBlock(); } @@ -1056,6 +1205,7 @@ void QCodeEdit::autoIndent() { QTextCursor tc = ui->textCode->textCursor(), stc = tc; tc.movePosition(QTextCursor::StartOfLine); if (!tc.movePosition(QTextCursor::Up)) return; + tc.beginEditBlock(); tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); QString line = tc.selectedText(), tabs; int i = 0; @@ -1071,6 +1221,7 @@ void QCodeEdit::autoIndent() { if (tabs.isEmpty()) return; stc.insertText(tabs); ui->textCode->setTextCursor(stc); + tc.endEditBlock(); } @@ -1508,6 +1659,17 @@ void QCodeEdit::textEdit_textChanged() { void QCodeEdit::textEdit_selectionChanged() { + if (hasBlockSelection()) { + QTextCursor tc = ui->textCode->textCursor(); + //qDebug() << block_start_cursor.selectionStart() << tc.selectionEnd(); + bool bs = ui->textCode->blockSignals(true); + tc.clearSelection(); + ui->textCode->setTextCursor(tc); + ui->textCode->blockSignals(bs); + applyExtraSelection(); + return; + } + block_start_cursor = QTextCursor(); es_selected.clear(); QString sf = ui->textCode->textCursor().selectedText(); if (sf.trimmed().isEmpty() || sf.contains("\n")) { diff --git a/libs/widgets/qcodeedit.h b/libs/widgets/qcodeedit.h index 1cd2be2..cc36f41 100644 --- a/libs/widgets/qcodeedit.h +++ b/libs/widgets/qcodeedit.h @@ -147,23 +147,33 @@ private: QMap ac_classes; QStringList cursor_scope; ACEntry link_entry, help_entry; + QTextCursor block_start_cursor; int prev_lc, auto_comp_pl, timer_parse, timer_blink, cur_search_ind, pos_press, pos_el_press; int cursor_width; bool spaces_, _ignore_focus_out, _first, _destructor, _replacing; - bool word_complete, help_visible, cursor_state; + bool word_complete, help_visible, cursor_state, block_sel_state; bool eventFilter(QObject * o, QEvent * e) override; void showEvent(QShowEvent * ) override; void timerEvent(QTimerEvent * ) override; void leaveEvent(QEvent * ) override; void changeEvent(QEvent * e) override; + bool codeKeyEvent(QKeyEvent * ke); void highlightBrackets(); void applyExtraSelection(); void clearSearch(); void moveToSearch(); int searchIndFromCursor(); + QRect cursorRect(QRect * line = 0); + QRect blockSelectionRect(); void repaintCursor(); void drawCursor(); + bool hasBlockSelection() const; + void startBlockSelection(); + void cancelBlockSelection(); + void switchBlockSelection(QKeyEvent * ke = 0); + bool removeBlockSelection(bool is_del); + void insertBlockSelection(QString text); ACEntry findEntryOnCursor(QTextCursor tc, int arg = -1, ACClass * acc = 0, QPair * scope = 0); private slots: